SpringBoot -- Security Management

Posted by richie19rich77 on Mon, 28 Feb 2022 16:48:17 +0100

1, Role inheritance

Generally speaking, there are relationships between roles, such as ROLE_admin generally has the authority of admin and user. So how to configure this role inheritance relationship? In Spring Security, developers only need to provide a role hierarchy. for example SpringBoot_ Management (II) As an example, assume ROLE_dba is the ultimate Boss with all permissions, ROLE_admin has ROLE_user's permission, ROLE_user is a public role, that is, ROLE_admin inherits ROLE_user,ROLE_dba inherits ROLE_admin, to describe this inheritance relationship, the developer only needs to provide a RoleHierarchy in the configuration class of Spring Security. The code is as follows:

    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }

After the RoleHierarchy is configured, there is a role_ Users in the DBA role can access all resources and have roles_ Users with the admin role can also access users with roles_ Resources that can only be accessed by the user role. combination SpringBoot_ Management (II) The instance test in the Controller shows that admin can access all users because it has dba permission.

2, Dynamically configure permissions

The authentication and authorization rules configured using HttpSecurity are still not flexible enough to realize the dynamic adjustment between resources and roles. If the URL permission is dynamically configured, the developer needs to customize the permission configuration. The configuration steps are as follows( SpringBoot_ Management (II) (completed on)

  1. Database design
    The database here is SpringBoot_ Management (II) A resource table and resource role association table are added on the basis of the database. As shown in the figure below, the resource table defines the URL mode that users can access, and the resource role table defines the role required to access the URL of the mode.

  2. Custom FilterInvocationSecurityMetadataSource
    To realize the dynamic configuration of permissions, you must first customize the FilterInvocationSecurityMetadataSource. In Spring Security, you can determine which roles are required for a request through the getAttributes method in the FilterInvocationSecurityMetadataSource interface. The default implementation class of the FilterInvocationSecurityMetadataSource interface is the implementation of DefaultFilterInvocationSecurityMetadataSource, Developers can define their own FilterInvocationSecurityMetadataSource. The code is as follows:
/**
 * The custom FilterInvocationSecurityMetadataSource mainly implements the getAttributes method in the interface,
 * The parameter of this method is a FilterInvocation, from which the URL of the current request can be extracted,
 * The return value is collection < configattribute >, which indicates the role required by the current request URL.
 */
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    //Create an ant pathmatcher, which is mainly used to realize ant style URL matching
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Resource
    MenuMapper menuMapper;
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //Extract the URL of the current request from the parameters
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        /**
         * Obtain all resource information from the database, that is, the menu table in this case and the role corresponding to the menu,
         * In the real environment, resource information can be cached in Redis or other cache databases.
         */
        List<Menu> allMenus = menuMapper.getAllMenus();
        /**
         * During the traversal process, obtain the role information required by the URL of the current request and return it. If the current request
         * The URL of does not have a corresponding pattern in the resource table. It is assumed that the request can be accessed after logging in, that is, directly
         * Return to ROLE_LOGIN. 
         */
        for (Menu menu: allMenus) {
            if (antPathMatcher.match(menu.getPattern(), requestUrl)) {
                List<Role> roles = menu.getRoles();
                System.out.println("roles = " + roles);
                String[] roleArr = new String[roles.size()];
                for (int i = 0; i < roleArr.length; i++) {
                    roleArr[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(roleArr);
            }
        }
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    /**
     * getAllConfigAttributes Method is used to return all defined permission resources,
     * Spring Security During startup, it will verify whether the relevant configuration is correct. If verification is not required, then the method
     * Just return null directly.
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * supports Method returns whether the class object supports verification
     */
    @Override
    public boolean supports(Class<?> clazz) {
       // return false;
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}


  1. Customize AccessDecisionManager
/**
 * Customize the AccessDecisionManager and override the decision method to determine the currently logged in user
 * Whether the role information required by the current request URL is available. If not, throw
 * AccessDeniedException Exception, otherwise do nothing.
 */
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {

    /**
     * decide Method has three parameters. The first parameter contains the information of the currently logged in user,
     * The second parameter is a FilterInvocation object, which can obtain the current request object, etc;
     * The third parameter is the return value of the getAttributes method in FilterInvocationSecurityMetadataSource
     * That is, the role required by the current request URL.
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        Collection<? extends GrantedAuthority> auths = authentication.getAuthorities();
        /**
         * Compare the role information. If the required role is ROLE_LOGIN, indicating the URL of the current request
         * If auth is an instance of UsernamePasswordAuthenticationToken
         * Then it indicates that the current user has logged in, and this method ends here. Otherwise, it enters the normal judgment process. If the current user
         * With the role required by the current request, the method ends.
         */
        for (ConfigAttribute configAttribute: configAttributes) {
            if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && authentication instanceof
                    UsernamePasswordAuthenticationToken)
                return ;
            for (GrantedAuthority authority: auths) {
                if (configAttribute.getAttribute().equals(authority.getAuthority()))
                    return ;
            }
        }
        throw new AccessDeniedException("Insufficient permissions");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
       // return false;
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
       // return false;
        return true;
    }
}
  1. MenuMapper and MenuMapper xml
  • MenuMapper
@Mapper
public interface MenuMapper {

    List<Menu> getAllMenus();
}
  • MenuMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.security1.dao.MenuMapper">
    <resultMap id="BaseResultMap" type="com.security1.vo.Menu" autoMapping="true">
       <id property="id" column="id"></id>
        <result property="pattern" column="pattern"/>
        <collection property="roles" ofType="com.security1.vo.Role" autoMapping="true">
            <id property="id" column="rid"></id>
            <result property="name" column="rname"></result>
            <result property="nameZh" column="rnameZh"></result>
        </collection>
    </resultMap>

    <select id="getAllMenus" resultMap="BaseResultMap">
                SELECT
            m.*,
            r.id AS rid,
            r.NAME AS rname,
            r.nameZh AS rnameZh
        FROM
            menu m
            LEFT JOIN menu_role mr ON m.id = mr.id
            LEFT JOIN role r ON m.id = r.id
    </select>
</mapper>

MenuMapper. BaseResultMap in XML

 <collection property="roles" ofType="com.security1.vo.Role" autoMapping="true">
            <id property="id" column="rid"></id>
            <result property="name" column="rname"></result>
            <result property="nameZh" column="rnameZh"></result>
        </collection>

Combined with the following figure.

  1. to configure
    Finally, configure the above two custom classes in Spring Security, as follows:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    protected  void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    protected  void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(cfisms());
                        object.setAccessDecisionManager(cadm());
                        return object;
                    }
                })
       /*         .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/db/**").hasRole("dba")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated()*/
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }

/*    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }*/

    @Bean
    CustomFilterInvocationSecurityMetadataSource cfisms() {
        return new CustomFilterInvocationSecurityMetadataSource();
    }

    @Bean
    CustomAccessDecisionManager cadm() {
        return new CustomAccessDecisionManager();
    }
}
  1. Menu
public class Menu {
    private Integer id;
    private String pattern;
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "Menu{" +
                "id=" + id +
                ", pattern='" + pattern + '\'' +
                ", roles=" + roles +
                '}';
    }
}

Topics: Java Spring Spring Boot security