Spring Secutity Custom Rights Configuration

Keywords: Java Spring xml Database

There are also many examples on the Srping Security Web, but basically all the resources are directly configured in the XML file, which is too restrictive to be flexible.What we need is that we can modify resource access in the background to take effect in real time in order to meet the needs of most systems today.

Dependencies to be introduced

    <!-- Spring security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>4.2.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>4.2.2.RELEASE</version>
    </dependency>
    <!--Spring Security end-->

User authentication

We customize an implementation class MUserDetailsService to implement the UserDetailsService interface.

There needs to be a loadUserByUsername method implemented to read the user's roles.
Here you need to query the user's information and role from the database through the user name
MGrantedAuthority implements the GrantedAuthority interface, which is used to build user permissions.
MUserDeatils implements the UserDeatils interface, which stores user information and permissions

Role of UserDetailsService in Identity Authentication

Authentication in Spring Security is the AuthenticationManager interface, which is a default implementation of ProviderManager, but it is not used to handle authentication, but is delegated to a configured AuthenticationProvider, where each AuthenticationProvider checks for authentication in turn.After checking, either return the Authentication object or throw an exception.Authentication is loading the UserDetails of the response to see if they match information such as the account, password, permissions entered by the user.This step is handled by the Dao Authentication Provider, which implements the Authentication Provider, which uses the UserDetailsService to authenticate user names, passwords, and authorizations.The UserDetails object containing GrantedAuthority fills in data when building Authentication objects.

The loadUserByUsername method in MUserDetailsService.class

    /**
     * Load user password and permission information based on user name
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //Query user information
        User user = userMapper.selectByName(username);
        List<Role> roleList = null;
        MUserDeatils userDeatils = null;
        if (user != null){
            //Query user's role
            roleList = roleMapper.queryByUser(user.getId());
            System.out.println("user" + user.getUsername() + "----" + user.getPassword());
            // Build Permissions
            Set<MGrantedAuthority> authorities = new HashSet<MGrantedAuthority>();
            if (roleList.size() != 0){
                for (Role role: roleList){
                    authorities.add(new MGrantedAuthority(role.getName()));
                    System.out.println(role.getName());
                }
                userDeatils = new MUserDeatils(user.getUsername(),user.getPassword(),authorities);

            }
        }
        return userDeatils;

    }

MGrantedAuthority.class

public class MGrantedAuthority implements GrantedAuthority {

    private String authority;

    public MGrantedAuthority(String authority){
        this.authority = authority;
    }

    @Override
    public String getAuthority() {
        return authority;
    }
}

MUserDeatils.class

Implement UserDetails interface and define variables

Read Resources and Owned Roles

A custom implementation class is required to implement the FilterInvocationSecurityMetadataSource interface.The corresponding relationship between resources and permissions can be achieved through the loadResourceDefine method.

For our custom FilterInvocationSecurityMetadataSource to take effect, we also need to define a MyFilterSecurityInterceptor class.
The data here needs to be retrieved from the database.Another custom interface, UrlMatcher, implements AntUrlPathMatcher.

pit

A tutorial on the Web puts the loadResourceDefine method in the constructor.However, after several trials, I encountered a problem where service,mapper could not be injected, and then a null pointer guidance exception was reported that service was not injected by debug.After several queries, it is known that the loadResourceDefine method is put into the construct before the injection execution, so the service and mapper in the function have not yet performed the injection, so an exception is reported for java.lang.NullPointerException.The solution is to put the loadResourceDefine method into the getAttributes method.

MFilterInvocationSecurityMetadataSource.class

    @Component
    public class MFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    public IRescAndRoleService iRescAndRoleService ;
    @Autowired
    private IUserService iUserService ;
    private UrlMatcher urlMatcher = new AntUrlPathMatcher();
    // Resource Permission Collection
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
    public void loadResourceDefine(){
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
        //Get User Information
        List<User> userList = iUserService.query();
       //Get a list of resources and roles
        List<RescAndRole> resourceList = iRescAndRoleService.query();
        System.out.println(resourceList);
        for (RescAndRole resource : resourceList) {
            Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
            atts.add(new SecurityConfig(resource.getRoleName() ));
            resourceMap.put(resource.getResString(), atts);
        }
    }
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        loadResourceDefine();//Prevent Failure to Inject
        // guess object is a URL.
        String url = ((FilterInvocation) o).getRequestUrl();
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next();
            if (urlMatcher.pathMatchesUrl(resURL, url)) {
                return resourceMap.get(resURL);
            }
        }
        return null;
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

AntUrlPathMatcher.class

public class AntUrlPathMatcher implements UrlMatcher {

    private boolean requiresLowerCaseUrl;
    private PathMatcher pathMatcher;

    public AntUrlPathMatcher() {
        this(true);
    }

    public AntUrlPathMatcher(boolean requiresLowerCaseUrl) {
        this.requiresLowerCaseUrl = true;
        this.pathMatcher = new AntPathMatcher();

        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public Object compile(String path) {
        if (this.requiresLowerCaseUrl) {
            return path.toLowerCase();
        }
        return path;
    }

    public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl) {
        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public boolean pathMatchesUrl(Object path, String url) {
        if (("/**".equals(path)) || ("**".equals(path))) {
            return true;
        }
        return this.pathMatcher.match((String) path, url);
    }

    public String getUniversalMatchPattern() {
        return "/**";
    }

    public boolean requiresLowerCaseUrl() {
        return this.requiresLowerCaseUrl;
    }

    public String toString() {
        return super.getClass().getName() + "[requiresLowerCase='"
                + this.requiresLowerCaseUrl + "']";
    }
}

MyFilterSecurityInterceptor.class

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter {
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public Class<? extends Object> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    public void invoke(FilterInvocation fi) throws IOException,
            ServletException {
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    public void setSecurityMetadataSource(
            FilterInvocationSecurityMetadataSource newSource) {
        this.securityMetadataSource = newSource;
    }

    public void destroy() {
    }

    public void init(FilterConfig arg0) throws ServletException {
    }

}

Decision Manager

Customize a decision manager MyAccessDecision Manager to implement the AccessDecision Manager interface.Where the decide method determines whether a user has access to a url

/* (non-Javadoc)
     * @see org.springframework.security.access.AccessDecisionManager#decide(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
     * This method determines whether the privilege has access to the resource. In fact, the object is the address of the resource, and authentication is the current user's
     * Corresponding permissions, if not logged in is a visitor, logged in is the corresponding permissions for that user
     */
    @Override
    public void decide(Authentication authentication, Object object,
                       Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        if(configAttributes == null) {
            return;
        }
        System.out.println(object.toString()); // object is a URL.
        //Permissions for the requested resource (one resource for multiple permissions)
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while(iterator.hasNext()) {
            ConfigAttribute configAttribute = iterator.next();
            //Permissions required to access the requested resource
            String needPermission = configAttribute.getAttribute();
            System.out.println("Visit"+object.toString()+"The required permissions are:" + needPermission);
            //User-owned privileges authentication
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for(GrantedAuthority ga : authorities) {
                if(needPermission.equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        //No privileges
        throw new AccessDeniedException(" No access! ");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        // TODO Auto-generated method stub
        return true;
    }

Configuration XML

web.xml

Adding a Seucrity filter will block all resource access

Be careful

    Can only be configured to /*
    
    <!--Load Security Profiles and mybatis configuration file-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            WEB-INF/config/security.xml
            WEB-INF/config/spring-mybatis.xml
        </param-value>
    </context-param>
    
    <!-- spring security Filter Configuration -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

spring-security.xml

<b:beans xmlns="http://www.springframework.org/schema/security"
         xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">


    <!--Landing Page Not Verified-->
    <http pattern="/userLogin.html" security="none" />
    <!--Static file request not validated-->
    <http pattern="/js/**" security="none" />
    <http pattern="/css/**" security="none" />
    <!--restful request-->
    <http pattern="/login" security="none" />
    <http pattern="/getGrid" security="none" />
    <!--The browser automatically requests the website icon: favicon.ico -No validation  -->
    <http pattern="/favicon.ico" security="none" />
    <http >

        <!--Pages displayed when custom permissions are insufficient-->
        <access-denied-handler error-page="/accessHint.html"></access-denied-handler>
        <!-- Customize login interface -->
        <form-login
                authentication-failure-url="/userLogin.html?error=true"
                login-page="/userLogin.html"
                default-target-url="/index.html"
                login-processing-url="/j_spring_security_check" />
        <logout invalidate-session="true"
                logout-success-url="/userLogin.html"
                logout-url="/j_spring_security_logout"/>
        <!-- By configuring custom-filter To add a filter, before="FILTER_SECURITY_INTERCEPTOR"Represents in SpringSecurity Execute before default filter. -->
        <custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
        <csrf disabled="true" />

    </http>

    <!-- Authentication filter -->
    <b:bean id="filterSecurityInterceptor"
                class="com.hand.security.utils.MyFilterSecurityInterceptor">
        <b:property name="rejectPublicInvocations" value="true"/>
        <!-- User-owned privileges -->
        <b:property name="accessDecisionManager" ref="accessDecisionManager" />
        <!-- Does the user have permission to the requested resource -->
        <b:property name="authenticationManager" ref="authenticationManager" />
        <!-- Resource-Permission Correspondence -->
        <b:property name="securityMetadataSource" ref="securityMetadataSource" />
    </b:bean>

    <!-- 2,Change how authentication information is loaded -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider
                user-service-ref="mUserDetailsService">
            <!--If the user's password is encrypted <password-encoder hash="md5" /> -->
        </authentication-provider>
    </authentication-manager>

    <!-- 1,Configure Custom Classes MUserDetailsService -->
    <b:bean id="mUserDetailsService" class="com.hand.security.service.impl.MUserDetailsService" />

    <!--Access the decision maker, determine the role a user has, and have sufficient privileges to access a resource -->
    <b:bean id="accessDecisionManager" class="com.hand.security.utils.MyAccessDecisionManager"></b:bean>

    <!--Resource Source Data Definition, which establishes a relationship between all resources and privileges, that is, to define which roles a resource can be accessed -->
    <b:bean id="securityMetadataSource" class="com.hand.security.utils.MFilterInvocationSecurityMetadataSource" ></b:bean>

</b:beans>

Posted by Adam W on Wed, 12 Jun 2019 10:53:49 -0700