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