1. Concepts related to rights management

Authority management is an almost all background system will involve an important part, the main purpose is to control the authority of the whole background management system. The common role-based access control authorization model is “user-role-permission”. Simply put, one user has multiple roles and one role has multiple permissions. Among them,

  • User: No need to say more, we also know;
  • Role: the concept of a set. Role management is a process to determine which rights a role has.
  • Permissions: 1). Page permissions: control which pages you can see and which pages you can’t see; 2). Operation rights, which control what operations you can do on the page (query, delete, edit, etc.); 3). Data permissions control what data you can see.

Essence is: Permission = Resource + Privilege Role = Set of permissions (a set of low-level permissions) User = A collection of high-level roles

Permission management process:

  1. Authentication management refers to the permission judgment logic, such as menu management (common service personnel cannot see the User Management menu after logging in to the system), function permission management (URL access management), and row-level permission management
  2. Authorization management refers to the permission assignment process. For example, the user is directly authorized, the rights directly assigned to the user have the highest priority, and the job of the user is authorized. The job information of the user can be regarded as a group, which functions like a role, but each user can be associated with only one job.

In the actual project, there are a large number of users, and it is extremely tedious to authorize each system user one by one. Therefore, we can learn from the Linux file management system and set the group mode. A group of multiple users can authorize the same permissions for user groups, which is much easier. In this mode: All rights of a user = Individual rights of a user + Rights of a user group The relationships among user groups, users, and roles are as follows:

Combined with the page permissions and operation permissions of permission management, such as menu access, function module operation, button operation, etc., functional operations and resources can be unified management, that is, they are directly associated with permissions, the diagram is as follows:

2. Authorization process analysis

2.1 Workflow of Authorized Access:

FilterSecurityInterceptor doFilter()->invoke() ->AbstractSecurityInterceptor beforeInvocation() ->SecurityMetadataSource GetAttributes () ->AccessDecisionManager() AccessDecisionManager() AccessDecisionManager -> AccessDecisionManagerCopy the code

The default authorization process uses this workflow, which then analyzes the functionality and source code of each component.

2.2 AbstractSecurityInterceptor analysis

FilterSecurityInterceptor for authorization interceptors, a wrapper in FilterSecurityInterceptor, request and the response of the filter chain FilterInvocation object, In FilterSecurityInterceptor, mainly by the invoke () call its parent class AbstractSecurityInterceptor method.

Invoke () analysis:

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

AbstractSecurityInterceptor The main methods of authorization filter beforeInvocation(),afterInvocation() and authenticateIfRequired(), the main methods of beforeInvocation() are analyzed as follows:

protected InterceptorStatusToken beforeInvocation(Object object) { .... / / from the SecurityMetadataSource permission attributes Collection < ConfigAttribute > attributes = this. ObtainSecurityMetadataSource () .getAttributes(object); if (attributes == null || attributes.isEmpty()) { ..... publishEvent(new PublicInvocationEvent(object)); return null; // No further work post-invocation} // Call authenticated (including user details) Authentication = authenticateIfRequired(); / / Attempt authorization try {/ / the key step: authorized the final decision of this. The accessDecisionManager. Decide (authenticated, object, attributes). } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); }}Copy the code

2.3 SecurityMetadataSource

SecurityMetadataSource loading ConfigAttribute from a database or other data source, in order to the AccessDecisionManager. Decide () for a match in the final decision. There are three methods:

Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException; Collection<ConfigAttribute> getAllConfigAttributes(); Supports (Class<? > var1);Copy the code

2.4 the AccessDecisionManager

The AccessDecisionManager is AbstractSecurityInterceptor interceptor invocation final access control decisions. And the GrantedAuthority in the Authentication Object created by AuthenticationManager is first read and used by the AccessDecisionManager in the authorization module. When a complex GrantedAuthority, getAuthority () is null, the AccessDecisionManager is required to specifically support the GrantedAuthority implementation in order to understand its contents.

AccessDecisionManager interface methods:

   void decide(Authentication authentication, Object secureObject,    Collection<ConfigAttribute> attrs) throws AccessDeniedException;

   boolean supports(ConfigAttribute attribute);

   boolean supports(Class clazz);
Copy the code

2.5 AccessDecisionVoter

The AccessDecisionManager. Decide to vote () will use the AccessDecisionVoter decisions. AccessDecisionVoter makes a vote access control decision and throws an AccessDeniedException if the access fails.

** AccessDecisionVoter**

   int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

   boolean supports(ConfigAttribute attribute);

   boolean supports(Class clazz);
Copy the code

The core method of AccessDecisionVoter vote() is to get the Authentication GrantedAuthority and match the ConfigAttributes defined by the AccessDecisionVoter. Discard the vote only when ConfigAttributes has no attribute.

Spring Security provides three voting methods to implement the AccessDecisionManager interface for voting access control decisions:

  • ConsensusBased: A majority of voters authorize access if they agree to it

  • AffirmativeBased: authorizes access as long as more than one voter agrees to access, all

  • Thursday, however, differs from Thursday, when both agree to authorize access

And the AccessDecisionVoter uses three static variables to represent voter voting:

  • ACCESS_ABSTAIN: waiver
  • ACCESS_DENIED: Access denied
  • ACCESS_GRANTED: Allows access

Note: when all voter abstention using variable allowIfEqualGrantedDeniedDecisions, true to pass, or false thrown AccessDeniedException.

In addition, the AccessDecisionManager implementation interface can be customized because some AccessDecisionVoter may have more weight than a higher vote or some AccessDecisionVoter may have one negative vote. The Spring Security implementation classes RoleVoter and AuthenticatedVoter for AccessDecisionVoter. RoleVoter is the most common AccessDecisionVoter. It is a simple permission voter with the prefix ROLE_.

Source code analysis:

Public int vote(Authentication authentication,Object object,Collection<ConfigAttribute>attributes){ If (authentication==null){return ACCESS_DENIED; } int result=ACCESS_ABSTAIN; Collection<? extendsGrantedAuthority>authorities=extractAuthorities(authentication); / / in order to vote for (ConfigAttributeattribute: attributes) {if (this. Supports (attribute)) {result = ACCESS_DENIED; //Attempt to find a matching granted authority for(GrantedAuthorityauthority:authorities){ if(attribute.getAttribute().equals(authority.getAuthority())){ returnACCESS_GRANTED; }}}}Copy the code

3. Case – Custom components

Custom components:

  1. Custom FilterSecurityInterceptor, can copy write FilterSecurityInterceptor, realize the abstract class AbstractSecurityInterceptor and Filter interface, Its main is to put the custom SecurityMetadataSource and customize the accessDecisionManager configuration to customize FilterSecurityInterceptor interceptors

  2. Custom SecurityMetadataSource, implementing an interface FilterInvocationSecurityMetadataSource, Implement loading configAttributes from a database or other data source

  3. A custom accessDecisionManager, which can implement official permission authentication based on the AccessDecisionVoter

  4. Custom AccessDecisionVoter

3.1 custom MyFilterSecurityInterceptor

Custom MyFilterSecurityInterceptor main work is:

  • Load custom SecurityMetadataSource to custom FilterSecurityInterceptor;

  • Load custom the AccessDecisionManager to custom FilterSecurityInterceptor;

  • Overriding the Invoke method

    @Component public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } private void invoke(FilterInvocation fi) throws IOException, ServletException {/ / fi / / url with a blocked inside inside calling MyInvocationSecurityMetadataSource getAttributes (Object Object) this method to get fi corresponding all permissions // Call MyAccessDecisionManager's Decide method to verify that the user is InterceptorStatusToken token = super.beforeInvocation(FI); Try {// Execute the next interceptor fi.getChain().dofilter (fi.getrequest (), fi.getresponse ()); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<? > getSecureObjectClass() { return null; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } / / set custom FilterInvocationSecurityMetadataSource @autowired public void setSecurityMetadataSource(MyFilterInvocationSecurityMetadataSource messageSource) { this.securityMetadataSource = messageSource; } // Set the custom accessDecisionManager@override @autoWired public void setAccessDecisionManager(AccessDecisionManager) accessDecisionManager) { super.setAccessDecisionManager(accessDecisionManager); }}Copy the code

3.2 custom MyFilterInvocationSecurityMetadataSource

Custom MyFilterInvocationSecurityMetadataSource main work is:

  • Load ConfigAttribute from the data source into the SecurityMetadataSource resource

  • Rewrite the getAttributes () loaded ConfigAttribute as the AccessDecisionManager. Decide to prepare () authorization decisions.

    @Component public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private Map<String, Collection<ConfigAttribute>> configAttubuteMap = null; Private void loadResourceDefine() {// Todo all permissions to load the database Collection<ConfigAttribute> Attributes; } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { AntPathRequestMatcher matcher; String resUrl; HttpServletRequest request = ((FilterInvocation) object).getRequest(); If (configAttubuteMap == null) {loadResourceDefine(); } Iterator<String> iterator = configAttubuteMap.keySet().iterator(); while (iterator.hasNext()) { resUrl = iterator.next(); matcher = new AntPathRequestMatcher(resUrl); if (matcher.matches(request)) { return configAttubuteMap.get(resUrl); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<? > clazz) { return FilterInvocation.class.isAssignableFrom(clazz); }}Copy the code

3.3 Customizing MyAccessDecisionManager

To customize MyAccessDecisionManager:

  • Rewrite the final authorization decision Decide () to customize the authorization access policy

    @Component public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { ConfigAttribute c; String needRole; if(null== configAttributes || configAttributes.size() <=0) { return; } / / 1. Get good resource permissions defined configuration Iterator < ConfigAttribute > iterable = configAttributes. The Iterator (); while (iterable.hasNext()){ c=iterable.next(); needRole=c.getAttribute(); / / 2. In turn than corresponding resource permissions for the user role (GrantedAuthority GrantedAuthority: authentication. GetAuthorities ()) { if(needRole.trim().equals(grantedAuthority.getAuthority())){ return; } } } } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<? > clazz) { return true; }Copy the code

    }

3.4 configuration SecurityConfig

Configure SecurityConfig as follows:

  • Will load WebSecurityConfig FilterSecurityInterceptor blocker

    Protected void configure(HttpSecurity HTTP) throws Exception {http.headers().frameoptions ().disable().and() // Form login .formLogin() .loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE) .loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL) SuccessHandler (authenticationSuccessHandler ()). FailureHandler (authenticationFailureHandler ()). And () / / application SMS authentication configuration . Apply (smsAuthenticationSecurityConfig). And () / / allows authorizeRequests () .antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL, SecurityConstants.APP_USER_REGISTER_URL, SecurityConstants.app_form_login_index_url).permitall ()// authentication is not required for all of the above requests. And ()// "rememberMe" configuration.rememberme () .tokenRepository(jdbcTokenRepository())// Token repository handler class TokenValiditySeconds (SecurityConstants. REMEMBER_ME_VERIFY_TIME) / / remember - me valid time Settings RememberMeParameter (SecurityConstants. REMEMBER_ME_PARAM_NAME) / / request parameter name set and (). CSRF (). The disable (); / / add custom permissions authorization interceptor HTTP. AddFilterBefore (myFilterSecurityInterceptor, FilterSecurityInterceptor. Class); }Copy the code

    conclusion

    Spring Security authorization process, can be involved in the main components mentioned above, the main thing is to follow the source code several times, understand the principle of it, in order to more fluent code code. Now that we have written the authentication and authorization analysis process for Spring Security, we will use the previous section to write a perfect permission management system for Spring Security.

Is everyone still ok? If you like, move your hands to show 💗, point a concern!! Thanks for your support!

Welcome to pay attention to the public number [Ccww technology blog], original technical articles launched at the first time