1. Introduction

I don’t know if you remember the 32nd Filter mentioned in Spring Security. It determines the permissions to access a particular path, the roles of the users who access it, and what are the permissions? What roles and permissions are required for access paths? It is FilterSecurityInterceptor, the wheel is exactly what we need.

2.FilterSecurityInterceptor

Filters # 32! Responsible for authentication of HTTP interface permissions. Let’s look at its filtering logic:

 	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 		FilterInvocation fi = new FilterInvocation(request, response, chain);
 		invoke(fi);
 	}
Copy the code

A FilterInvocation invocation is initialized and handled by the Invoke method:

	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if((fi.getRequest() ! =null) && (fi.getRequest().getAttribute(FILTER_APPLIED) ! =null)
				&& observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			// first time this request being called, so perform security checking
			if(fi.getRequest() ! =null && observeOncePerRequest) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}

			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null); }}Copy the code

Every time the request is Filter flagged FILTER_APPLIED, the unflagged beforeInvocation method from the parent and then into the Filter chain looks like a pre-invocation. So what’s being pre-processed? First by enclosing obtainSecurityMetadataSource (). The getAttributes (Object Object) with the protected objects (that is, the current request URI) all the mapping characters (ConfigAttribute Directly understood as further abstraction of the role). The AccessDecisionManager, AccessDecisionManager, is then used to make a voting decision to determine whether to permit or not. Let’s look at these two interfaces.

Security interceptor and “security object” model reference:

3. Metadata loader

Meta data loader is FilterInvocationSecurityMetadataSource FilterSecurityInterceptor properties, UML diagram is as follows:

FilterInvocationSecurityMetadataSource is a marker interface, its abstract methods inherited from SecurityMetadataSource ` ` AopInfrastructureBean. Its purpose is to retrieve the resource role metadata described in our previous article.

  • Collection getAttributes(Object Object) Gets all the roles configured by the URI based on the provided information about the protected Object
  • Collection getAllConfigAttributes() gets all the characters
  • boolean supports(Class<? > clazz)Whether to provide for a particular security objectConfigAttributesupport

3.1 Implementation Roadmap for customization

All ideas are for reference only, in fact, your business shall prevail!

Collection

getAttributes(Object Object) The URI in the request must be obtained to Match the Ant Pattern in all resource configurations to obtain the corresponding resource configuration. Here, the resource configuration queried by the resource query interface needs to be encapsulated as AntPathRequestMatcher to facilitate Ant Match. It’s important to note that if you’re using Restful style, add, delete, modify, and review will be very convenient for you to control your resources. Reference implementation:

 @Bean
 public RequestMatcherCreator requestMatcherCreator(a) {
   return metaResources -> metaResources.stream()
           .map(metaResource -> new AntPathRequestMatcher(metaResource.getPattern(), metaResource.getMethod()))
           .collect(Collectors.toSet());
 }
Copy the code

After the HttpRequest matches the corresponding resource configuration, it can obtain the corresponding role set according to the resource configuration. These roles are voted on by the AccessDecisionManager, the AccessDecisionManager, to permit or not.

4. Decision manager

The AccessDecisionManager is used to vote on whether to approve the request.

  public interface AccessDecisionManager {
    // Decisions are made mainly by the AccessDecisionVoter it holds
   	void decide(Authentication authentication, Object object, Collection
       
         configAttributes)
        throws AccessDeniedException,
   			InsufficientAuthenticationException;
   // to determine whether the AccessDecisionManager can handle the passed ConfigAttribute
   	boolean supports(ConfigAttribute attribute);
   // To ensure that the AccessDecisionManager is configured to support the security Object type to be rendered by the security interceptor.
   	boolean supports(Class
        clazz);
   }
Copy the code

AccessDecisionManager has three default implementations:

  • Affirmative-based decision maker. Users can pass by having a role that agrees to access.
  • Consensus-based decision maker. The number of approved roles is greater than the number of prohibited roles.
  • Both a consensus – based decision maker. Only when all roles held by a user agree to access the system can the system be allowed to access the system.

Voting decision model Reference:

4.1 Custom decision manager

Dynamically controlling permissions requires us to implement our own access decision maker. As mentioned above, there are three implementations by default. Here, I choose the affirmative decision based on AffirmativeBased, as long as the user has a role which contains the resource he/she wants to access, he/she can access the resource. The next step is the definition of AccessDecisionVoter. You can choose a built-in voter

5. Decision voting machines

AccessDecisionVoter parses the security configuration property ConfigAttribute with specific logic and votes based on specific policies. The total number of yes votes is +1, and the total number of no votes is -1. The total number of votes for abstention is +0, and the AccessDecisionManager decides whether to release the vote based on the specific counting strategy.

5.1 Role Voting Machine

The most common voter provided by Spring Security is the RoleVoter RoleVoter, which treats the Security configuration property ConfigAttribute as a simple role name and grants access when the user is assigned that role. If any ConfigAttribute starts with the prefix ROLE_, it will vote. If there is a GrantedAuthority that returns a string (via the getAuthority() method) exactly equal to one or more ConfigAttributes starting with the prefix ROLE_, it will vote to grant access. If there is no ConfigAttributes match starting with ROLE_, RoleVoter votes to deny access. If no ConfigAttribute is prefixed with ROLE_, the attribute is abstained. This is exactly the voting machine we want.

5.2 Role Hierarchical Voting machine

It is often required that certain roles in an application should automatically “include” other roles. For example, in an application with the ROLE_ADMIN and ROLE_USER role concepts, you might want the administrator to be able to perform all the actions that ordinary users can perform. You have to nest all sorts of complicated logic to meet this requirement. Thankfully, RoleHierarchyVoter can help reduce that burden. RoleHierarchy is a verb derived from RoleVoter. RoleHierarchy can be implemented by configuring RoleHierarchy The one on the left must have access to the one on the right. The specific configuration rules are as follows: Roles are connected from left to right and from high to low with > (note the two Spaces), and newline character \n is used as the dividing line. For example

   ROLE_ADMIN > ROLE_STAFF
   ROLE_STAFF > ROLE_USER
   ROLE_USER > ROLE_GUEST
Copy the code

Note that you need to implement your own logic for role layering in dynamic configuration. This style is not implemented in the DEMO.

Configuration of 6.

There are two aspects to configuration.

6.1 Configuring Customized Components

We need to inject the metadata loader and access decision into Spring IoC:

 /** * Dynamic permission component configuration **@author Felordcn
  */
 @Configuration
 public class DynamicAccessControlConfiguration {
     /** * RequestMatcher@return RequestMatcher
      */
     @Bean
     public RequestMatcherCreator requestMatcherCreator(a) {
         return metaResources -> metaResources.stream()
                 .map(metaResource -> new AntPathRequestMatcher(metaResource.getPattern(), metaResource.getMethod()))
                 .collect(Collectors.toSet());
     }

     /** * Metadata loader **@return dynamicFilterInvocationSecurityMetadataSource
      */
     @Bean
     public FilterInvocationSecurityMetadataSource dynamicFilterInvocationSecurityMetadataSource(a) {
         return new DynamicFilterInvocationSecurityMetadataSource();
     }

     /** ** role voter *@return roleVoter
      */
     @Bean
     public RoleVoter roleVoter(a) {
         return new RoleVoter();
     }

     /** * Affirmative based access decision **@paramDecisionVoters AccessDecisionVoter beans are automatically injected into decisionVoters *@return affirmativeBased
      */
     @Bean
     public AccessDecisionManager affirmativeBased(List
       
        > decisionVoters)
       > {
         return newAffirmativeBased(decisionVoters); }}Copy the code

Spring Security’s Java Configuration does not expose every property for every object it configures. This simplifies configuration for most users. While there are good reasons not to expose each property directly, users may still need to implement personalized requirements as in this article. To solve this problem, Spring Security introduced the concept of ObjectPostProcessor, which can be used to modify or replace many Object instances created by Java Configuration. FilterSecurityInterceptor replace configuration is carried out in this way:

@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class CustomSpringBootWebSecurityConfiguration {
    private static final String LOGIN_PROCESSING_URL = "/process";

    /**
     * Json login post processor json login post processor.
     *
     * @return the json login post processor
     */
    @Bean
    public JsonLoginPostProcessor jsonLoginPostProcessor(a) {
        return new JsonLoginPostProcessor();
    }

    /**
     * Pre login filter pre login filter.
     *
     * @param loginPostProcessors the login post processors
     * @return the pre login filter
     */
    @Bean
    public PreLoginFilter preLoginFilter(Collection<LoginPostProcessor> loginPostProcessors) {
        return new PreLoginFilter(LOGIN_PROCESSING_URL, loginPostProcessors);
    }

    /** * Jwt authentication filter. **@paramThe jwtTokenGenerator JWT utility class is responsible for generating validation parsing *@paramJwtTokenStorage JWT Cache storage interface *@return the jwt authentication filter
     */
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter(JwtTokenGenerator jwtTokenGenerator, JwtTokenStorage jwtTokenStorage) {
        return new JwtAuthenticationFilter(jwtTokenGenerator, jwtTokenStorage);
    }

    /** * The type Default configurer adapter. */
    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtAuthenticationFilter jwtAuthenticationFilter;
        @Autowired
        private PreLoginFilter preLoginFilter;
        @Autowired
        private AuthenticationSuccessHandler authenticationSuccessHandler;
        @Autowired
        private AuthenticationFailureHandler authenticationFailureHandler;
        @Autowired
        private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
        @Autowired
        private AccessDecisionManager accessDecisionManager;

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            super.configure(auth);
        }

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

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .cors()
                    .and()
                    // The session generation policy uses stateless policy
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
                    .and()
                    // Dynamic permission configuration.authorizeRequests().anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor() ) .and() .addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)/ / JWT must be configured before the UsernamePasswordAuthenticationFilter
                    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                    // An error message is returned if the JWT token fails.formLogin().loginProcessingUrl(LOGIN_PROCESSING_URL).successHandler(authenticationSuccessHandler).failureHandler(authen ticationFailureHandler) .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessHandler(new CustomLogoutSuccessHandler());

        }

        / * * * custom FilterSecurityInterceptor ObjectPostProcessor to replace the default configuration to achieve the purpose of dynamic access * *@return ObjectPostProcessor
         */
        private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor(a) {
            return new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                    object.setAccessDecisionManager(accessDecisionManager);
                    object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                    returnobject; }}; }}}Copy the code

Then you write a Controller method to register it in the database as a resource for dynamic access control. No annotations or more detailed Java Config configuration are required.

7. To summarize

There have been 10 demos since the beginning. We have moved from how to learn Spring Security to implementing rBAC-based, dynamic access control of privileged resources. If you can make it this far, you’ve met some basic development customization needs. Of course, there are many local concepts of Spring Security that I will cover in the future.

8. roadmap

I’ll catch my breath for a few days. Some of the upcoming Spring Security tutorials will focus on the more popular OAuth2.0, SSO, and OpenID. Please pay attention to felord.cn

Same rules, follow Felordcn reply day10 get DEMO.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn