Implementation of permission management based on spring security

First think about it. If you don't use the framework and let yourself implement the permission management function, how many keywords can you jump out of your mind?

  • Filter
  • Interceptor

These two concepts should be thought of in an instant. They can help us realize the permission function. One is the specification of servlet and the other is the concept of spring. In the system execution sequence, Filter is executed before Interceptor. As for more detailed comparison, there are a lot of information on the Internet.

Implementation process of spring security for permission

Let's first understand some important points in it:

  • Filter
  • AbstractSecurityInterceptor (the real interceptor of authority management security, and bound with AccessDecisionManager (handling authority authentication) and FilterInvocationSecurityMetadataSource (providing metadata of request path and authority name))
  • FilterInvocationSecurityMetadataSource (it mainly realizes the function path and name of loading cache permissions, and provides the ability to find the permission name from the request path for subsequent decision managers to determine.)
  • AccessDecisionManager (mainly determines whether the user has the decision-making method within the permission, has the permission to release, and refuses access without permission.)
  • WebSecurityConfigurerAdapter (configuration class)
  • AccessDeniedHandler

Specific code implementation

Because I'm talking about the whole process from the forward process, if you copy the code in order, some attributes that need to be injected will always display red, indicating that you can't find this class. Don't worry, because these classes are in the following code. It's recommended to browse the article as a whole and copy all the code directly for debugging after understanding it.

First, implement our own WebSecurityConfigurerAdapter. Note auth Userdetailsservice and HTTP The addfilterbefore methods are two important points in the overall process, which are the permissions corresponding to the current user and the request path implemented by ourselves.

@Configuration
@EnableWebSecurity
public class WebSecurityConfigur extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAbstractSecurityInterceptor myAbstractSecurityInterceptor;
    @Autowired
    @Qualifier("myUserDetailsService")
    private UserDetailsService myUserDetailsService;
    @Autowired
    private CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new MyPasswordEncoder();
    }

    /**
     * Put the UserDetailsService object implemented by ourselves into auth
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
        ;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()//Open simulation request
                .authorizeRequests()
                .and()
                .formLogin()
                .permitAll()//The login page can be accessed by any user
                .successHandler(myAuthenticationSuccessHandler)//Login success processing logic
                .failureHandler(myAuthenticationFailureHandler)//Login failure processing logic
                .and()
                .logout()//Log out
                .permitAll()
                .deleteCookies("JSESSIONID")//Delete cookie s after logout
                .and()
                .headers()
                .frameOptions().sameOrigin() //Logoff behavior arbitrary access
                .and().exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);
        //Put our own interceptor into HttpSecurity
        http.addFilterBefore(myAbstractSecurityInterceptor, FilterSecurityInterceptor.class)
        ;
    }
}

Then implement a subclass of UserDetailsService, MyUserDetailsService, and override the loadUserByUsername() method. This method will be called when the user logs in. Search MyUserDetailsService in the full text of the current article. I'll go first. Since I haven't integrated mybatis, I'll write the dead data first. After the integration, I'll revise this part of the article again.

It is assumed that the permissions found in loadUserByUsername are "customer" and "all".

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    @Qualifier("myPasswordEncoder")
    private PasswordEncoder passwordEncoder;
    @Autowired
    ControlAccount controlAccount;
    //Account not locked
    Boolean accountNonLocked = true;

    /**
     * @param username
     * @return
     * @throws UsernameNotFoundException
     * @description This method will be executed when the user logs in
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //Check whether the current account is locked. Three times is locked
        Integer num = controlAccount.getLockTable().get(username);
        if (null != num && 3 <= num.intValue()) {
            accountNonLocked = false;
        }
        //Find out the characters you own by name

        //Find out the corresponding permission according to the role

        //Construct each permission, such as SimpleGrantedAuthority
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if ("account".equals(username)) {
            GrantedAuthority grantedAuthority1 = new SimpleGrantedAuthority("customer");
            GrantedAuthority grantedAuthority2 = new SimpleGrantedAuthority("all");
            grantedAuthorities.add(grantedAuthority1);
            grantedAuthorities.add(grantedAuthority2);
        } else {
            throw new BadCredentialsException("user does not exist");
        }
        //Return the user object of security
        //this(username, password, true, true, true, true, authorities);
        User user = new User("account", passwordEncoder.encode("wushichao")
                , true, true, true, accountNonLocked, grantedAuthorities);

        return user;
    }

Then there is an important implementation class: MyAbstractSecurityInterceptor. We need to rewrite its doFilter and obtainSecurityMetadataSource methods. At the same time, we need to insert MyAccessDecisionManager into this class. Forget the students of SecurityMetadataSource and AccessDecisionManager. Search these two keywords in this article and recall their functions.

@Service
public class MyAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    @Autowired
    MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation filterInvocation = new FilterInvocation(servletRequest, servletResponse, filterChain);
        //In beforeInvocation, getAttributes of FilterInvocationSecurityMetadataSource will be called to obtain the permission corresponding to the url
        //And loadUserByUsername calling UserDetailsService to obtain the permission corresponding to the user name
        //Then call MyAccessDecisionManager's decide to compare.
        InterceptorStatusToken interceptorStatusToken = super.beforeInvocation(filterInvocation);
        try {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } finally {
            super.afterInvocation(interceptorStatusToken, null);
        }

    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.myFilterInvocationSecurityMetadataSource;
    }
}

**Finally, there are two implementation classes left. One is MyFilterInvocationSecurityMetadataSource, which rewrites the getAttributes(XX) method. This method is used to find out the permissions corresponding to the path according to the request path. It will be used to compare with the permissions owned by the user later** Don't forget to return true for the supports method

@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    Log log = LogFactory.getLog(MyFilterInvocationSecurityMetadataSource.class);

    @Autowired
    PermisionDao permisionDao;
    private HashMap<String, Collection<ConfigAttribute>> map = null;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        log.info("MyFilterInvocationSecurityMetadataSource:getAttributes come in");
        //1. Obtain the permissions corresponding to all paths
        loadAllUrlResouceAuth();
        List<ConfigAttribute> list = new ArrayList<ConfigAttribute>();
        //2 remove the request request from the filter
        HttpServletRequest httpRequest = ((FilterInvocation) o).getHttpRequest();
        //3 matching
        AntPathMatcher pathMatcher = new AntPathMatcher();
        Iterator<String> iterator = map.keySet().iterator();
        Collection<ConfigAttribute> collection = null;
        while (iterator.hasNext()) {
            String pattern = iterator.next();
            String path = httpRequest.getRequestURI().toString();
            if (pathMatcher.match(pattern, path)) {
                log.info("MyFilterInvocationSecurityMetadataSource:getAttributes There are matching paths and permissions");
                collection = map.get(pattern);
            }
        }
        //When the collection is null, it will not enter the Manager
        log.info("MyFilterInvocationSecurityMetadataSource:getAttributes  -collection:" + JSON.toJSONString(collection));
        return collection;
    }

    private void loadAllUrlResouceAuth() {
        map = new HashMap<>();
        //Find out all permission data sets from the database
        List<Permision> permisionList = permisionDao.findAll();
        ConfigAttribute cfg;
        Collection<ConfigAttribute> array;
        //Here, we take the url as k and the permission name as v
        for (Permision permision : permisionList) {
            array = new ArrayList<>();
            cfg = new SecurityConfig(permision.getName());
            array.add(cfg);
            map.put(permision.getUrl(), array);
        }
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

The other is MyAccessDecisionManager. We need to rewrite its decision method and the other two supports methods. Pay attention to changing its return to true. In the decision method, we need to compare the permissions corresponding to the request path with the permissions owned by the user. If there is a match, it means there is permission, and if there is no match, it means there is no permission.

@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
    Log log = LogFactory.getLog(MyAccessDecisionManager.class);
    @Autowired
    MyUserDetailsService myUserDetailsService;

    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        log.info("MyAccessDecisionManager:decide come in");
        //If the request path does not match the data in the permission table, it will be released directly
        if (null == collection || 0 == collection.size()) {
            log.info("MyAccessDecisionManager:decide collection isnull");
            return;
        }
        ConfigAttribute configAttribute;

        //Compare the permissions matched to the request path with the permissions matched to the user. If there is one permission, it can be released
        for (Iterator<ConfigAttribute> iterator = collection.iterator(); iterator.hasNext(); ) {
            configAttribute = iterator.next();
            String attribute = configAttribute.getAttribute();
            for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
                if (attribute.trim().equals(grantedAuthority.getAuthority())) {
                    log.info("MyAccessDecisionManager:decide Have matching permissions");
                    return;
                }
            }
        }
        //If the front is not stopped, the error prompt will be directly reported at last without permission. After throwing an exception here, restart the error handling request, print the log in MyAbstractSecurityInterceptor, and you will find that it enters twice
        //Print out httprequest Getrequesturl () will find that its path is / error
        log.info("MyAccessDecisionManager:decide no right");
        throw new AccessDeniedException("no right");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

Request message

Login interface of security

http://localhost:8080/login?username=account&password=wushichao1

I wrote the address of the controller myself / customer/login

http://localhost:8080/customer/login

ending

Well, the above is all the code to realize the permission. Because the tool class, dao layer query class, enumeration class, etc. are designed in it, in order not to affect everyone's thinking, I didn't post it here. I put the code download link below, and the students who need it can download it by themselves. The code inside is debugged successfully.

Download link: https://github.com/longqiyuye925/blogServer

Keywords: Spring Security

Added by belphegor on Fri, 28 Jan 2022 11:20:21 +0200