编程知识 cdmana.com

Deeply understand the principle of spring security authorization mechanism

Original / Zhu Jiqian

stay Spring Security Within the framework of licensing rights , For the back end http The interface implements permission authorization control , There are two ways to do it .

One 、 One is based on annotation method level authentication , among , There are two ways of annotation @Secured and @PreAuthorize Two kinds of .

@Secured Such as :

  1 @PostMapping("/test")  2  @Secured({WebResRole.ROLE_PEOPLE_W})  3  public void test(){  4  ......  5  return null;  6  }

@PreAuthorize Such as :

  1 @PostMapping("save")  2 @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')")  3 public RestResponse save(@RequestBody @Validated SysUser sysUser, BindingResult result) {  4     ValiParamUtils.ValiParamReq(result);  5     return sysUserService.save(sysUser);  6 }

 

Two 、 One is based on config Configuration class , Need to be corresponding to config Class configuration @EnableGlobalMethodSecurity(prePostEnabled = true) The annotation will take effect , The way of license control is as follows :

  1 @Override  2 protected void configure(HttpSecurity httpSecurity) throws Exception {  3     // It uses JWT, Ban csrf  4     httpSecurity.cors().and().csrf().disable()  5             // Permission authentication is required for setting request   6             .authorizeRequests()  7             // Home page and login page   8             .antMatchers("/").permitAll()  9             .antMatchers("/login").permitAll() 10             //  All other requests require authentication  11             .anyRequest().authenticated(); 12     // Exit login processing  13     httpSecurity.logout().logoutSuccessHandler(...); 14     //token Verify the filter  15     httpSecurity.addFilterBefore(...); 16 }

Each of these two ways has its own characteristics , In daily development , Ordinary programmers have more contact with , The interface permission control in annotation mode .

That's the problem , We configure these annotations or classes , Its security Box is how to help achieve specific back-end API The interface is used for permission control ?

From a single line @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')") From the notes , I can't see any clue , To answer this question , It also needs to go deep into the source code level , To be able to security The authorization mechanism is better understood .

To give a general overview of the process , The author makes a brief summary with his own thinking , It can be explained in a few words , Take this interface as an example :

  1 @PostMapping("save")  2 @PreAuthorize("hasAuthority('sys:user:add')")  3 public RestResponse save(@RequestBody @Validated SysUser sysUser, BindingResult result) {  4     ValiParamUtils.ValiParamReq(result);  5     return sysUserService.save(sysUser);  6 }

namely , Authenticated users , Make a request to access “/save” Interface , If url The request is set in the configuration class to have permission authentication , Will be security Frame usage filter The interceptor intercepts and authenticates the request . The interception process is mainly an action , It is to set the permission of the request with @PreAuthorize Set the permission character “sys:user:add” Match , If it matches , Indicates that the request is an ownership call “/save” Permission for interfaces , So , Can be allowed to execute the interface resource .

 

stay springboot+security+jwt In the frame , Through a series of built-in or self-defined filters Filter To achieve license control , How to set a custom filter Filter Well ? for example , Can be set by httpSecurity.addFilterBefore(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) From the definition of a based on JWT Intercepted filters JwtFilter, Here addFilterBefore Methods will be analyzed in detail in the next article , It's not going to start here , The general meaning of this method is , The filter will be customized JwtFilter Add to Security In the frame , Become one of the priority security Filter, The code level is to add a custom filter to List<Filter> filters.

 

Set to add self-defined filters Filter The virtual code is as follows :

  1 @Configuration  2 @EnableWebSecurity  3 @EnableGlobalMethodSecurity(prePostEnabled = true)  4 public class SecurityConfig extends WebSecurityConfigurerAdapter {  5     ......  6     @Override  7     protected void configure(HttpSecurity httpSecurity) throws Exception {  8         // It uses JWT, Ban csrf  9         httpSecurity.cors().and().csrf().disable() 10                 // Permission authentication is required for setting request  11                 .authorizeRequests() 12                 ...... 13                 // Home page and login page  14                 .antMatchers("/").permitAll() 15                 .antMatchers("/login").permitAll() 16                 //  All other requests require authentication  17                 .anyRequest().authenticated(); 18         ...... 19         //token Verify the filter  20         httpSecurity.addFilterBefore(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); 21     } 22 }

The filter class extrends Inherit BasicAuthenticationFilter, and BasicAuthenticationFilter It's inheritance OncePerRequestFilter, The filter ensures that only one request passes through filter, It doesn't have to be repeated . After this configuration , When the request comes , Will be automatically JwtFilter Class interception , At this time , The overridden doFilterInternal Method , stay SecurityContextHolder.getContext().setAuthentication(authentication) After passing the certification , Will execute the filter chain FilterChain Methods chain.doFilter(request, response);

  1 public class JwtFilter  extends BasicAuthenticationFilter {  2   3     @Autowired  4     public JwtFilter(AuthenticationManager authenticationManager) {  5         super(authenticationManager);  6     }  7   8    @Override  9    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 10        //  Get token,  Log in and check the status  11        //  Get the token and get the login authentication information according to the token  12        Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request); 13        //  Set login authentication information to context  14        SecurityContextHolder.getContext().setAuthentication(authentication); 15  16        chain.doFilter(request, response); 17    } 18  19 }

So , Here comes the question , Filter chain FilterChain What is it ?

Here , First click in and take a look at the class source code :

  1 package javax.servlet;  2   3 import java.io.IOException;  4   5 public interface FilterChain {  6     void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;  7 }

FilterChain Only one doFilter Method , The function of this method is to request request Forward to the next filter filter Carry out filtering operation , The execution process is as follows :

The filter chain is like an iron chain , Connect the relevant filters , Request threads are like ants , It's going to climb all the way down this chain ----- namely , Through chain.doFilter(request, response) Method , Pass on layer by layer like a nest , When passed to the last filter corresponding to the request , The processed request will be forwarded back to . therefore , Through the filter chain , Can be implemented in different filters for requests request Deal with , And the filters don't interfere with each other .

This is actually a design pattern of responsibility chain . In this model , Usually each recipient contains a reference to another receiver . If an object cannot process the request , So , It will pass the same request to the next recipient , And so on .

 

Spring Security What are the filters on the filter chain of the frame ?

 

Can be in DefaultSecurityFilterChain Class depends on the output log perhaps debug To examine Security What filters do you have , If in DefaultSecurityFilterChain Break point in constructor in class , As shown in the figure , You can see , Custom JwtFilter The filter also includes :

These filters are all on the same filter chain , That is, through chain.doFilter(request, response) Requests can be forwarded layer by layer , The main filter that handles whether the request interface is authorized is FilterSecurityInterceptor, Its main functions are as follows :

1. Get the permission information of the interface to be accessed , namely @Secured({WebResRole.ROLE_PEOPLE_W}) or @PreAuthorize Defined license information ;

2. According to SecurityContextHolder Stored in authentication User information , To determine whether the permission information of the interface to be accessed is included , If included , It means that you have permission to use the interface ;

3. The main authorization function is in the parent class AbstractSecurityInterceptor Implementation in ;

  

We will be from FilterSecurityInterceptor Here we start to focus on the analysis of Security The realization of the principle of authorization mechanism .

The filter chain forwards the request FilterSecurityInterceptor When , Will execute FilterSecurityInterceptor Of doFilter Method :

  1 public void doFilter(ServletRequest request, ServletResponse response,  2       FilterChain chain) throws IOException, ServletException {  3    FilterInvocation fi = new FilterInvocation(request, response, chain);  4    invoke(fi);  5 }

In this code ,FilterInvocation Class is an interesting existence , In fact, its function is very simple , Is to pass the previous filter to the filter request,response,chain Copy and save to FilterInvocation Li , Dedicated to FilterSecurityInterceptor Filter use . What's interesting about it is that , It is to generalize multiple arguments into one class , It plays a role in unified management , Would you , if N Multiple arguments , They come in and they're scattered all over the class , There are too many arguments , More code , When the method is too dispersed , It may easily lead to the reading process , Get confused where these arguments come from . But if it's unified into a class , You can quickly locate the source , Easy code reading . Someone on the Internet mentioned that FilterInvocation Classes also decouple , That is, avoid using the same reference variable as other filters .

All in all , The setting of this place is simple , But it's worth learning , Apply its ideas to practical development , It's also a way to simplify code .

FilterInvocation The main source code is as follows :

  1 public class FilterInvocation {  2   3    private FilterChain chain;  4    private HttpServletRequest request;  5    private HttpServletResponse response;  6   7   8    public FilterInvocation(ServletRequest request, ServletResponse response,  9          FilterChain chain) { 10       if ((request == null) || (response == null) || (chain == null)) { 11          throw new IllegalArgumentException("Cannot pass null values to constructor"); 12       } 13  14       this.request = (HttpServletRequest) request; 15       this.response = (HttpServletResponse) response; 16       this.chain = chain; 17    } 18    ...... 19 }

FilterSecurityInterceptor Of doFilter Call... In the method invoke(fi) Method :

  1 public void invoke(FilterInvocation fi) throws IOException, ServletException {  2    if ((fi.getRequest() != null)  3          && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)  4          && observeOncePerRequest) {  5      // Filter is already applied to this request , Once per request , So there's no need for a new safety check    6       fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  7    }  8    else {  9       //  The first time this request is called , Safety checks need to be carried out  10       if (fi.getRequest() != null && observeOncePerRequest) { 11          fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); 12       } 13        //1. Authorizing the specific implementation of the entry  14       InterceptorStatusToken token = super.beforeInvocation(fi); 15       try { 16        //2. Business performed after authorization  17          fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 18       } 19       finally { 20          super.finallyInvocation(token); 21       } 22        //3. Follow up treatment  23       super.afterInvocation(token, null); 24    } 25 }

The entry point of authorization mechanism implementation is super.beforeInvocation(fi), Its concrete implementation is in the parent class AbstractSecurityInterceptor Implementation in ,beforeInvocation(Object object) The implementation of the system mainly includes the following steps :

 

One 、 Get permission to the interface you want to access , Here debug An example of this is calling the “/save” Interface , Its permission is set as follows @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')"), According to the screenshot below , We know the variables attributes Permission to the request interface has been obtained :

Two 、 Get the certification and store it in SecurityContextHolder User information , among ,authorities It's a collection of all permissions owned by users ;

Here authenticateIfRequired() Method core implementation :

  1 private Authentication authenticateIfRequired() {  2    Authentication authentication = SecurityContextHolder.getContext()  3          .getAuthentication();  4    if (authentication.isAuthenticated() && !alwaysReauthenticate) {  5      ......  6       return authentication;  7    }  8    authentication = authenticationManager.authenticate(authentication);  9    SecurityContextHolder.getContext().setAuthentication(authentication); 10    return authentication; 11 }

After the certification process has passed , Execute SecurityContextHolder.getContext().setAuthentication(authentication) Store user information in Security In the framework , Then you can go through SecurityContextHolder.getContext().getAuthentication() Get the stored user information ;

 

3、 ... and 、 Try to authorize , User information authenticated、 Request to bring object information object、 Permission information for the interface being accessed attributes, Into decide Method ;

decide() It's the decision Manager AccessDecisionManager A way to define .

  1 public interface AccessDecisionManager {  2    void decide(Authentication authentication, Object object,  3          Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,  4          InsufficientAuthenticationException;  5    boolean supports(ConfigAttribute attribute);  6    boolean supports(Class<?> clazz);  7 }

AccessDecisionManager It's a interface Interface , This is the core of the authorization system .FilterSecurityInterceptor In Authentication , By calling AccessDecisionManager Of decide() Methods to make authorization decisions , If it can pass , The corresponding interface can be accessed .

AccessDecisionManager Class methods are implemented in subclasses , contain AffirmativeBased、ConsensusBased、UnanimousBased Three subclasses ;

AffirmativeBased By one vote , This is AccessDecisionManager Presupposition class ;

ConsensusBased The minority is subordinate to the majority ;

UnanimousBased Express one vote against ;

How to understand this voting mechanism ?

Click in AffirmativeBased Class , You can see that there is a line of code inside int result = voter.vote(authentication, object, configAttributes):

Here AccessDecisionVoter It's a voting machine , Using the delegate design pattern , namely AffirmativeBased Class will delegate the voting machine to the election , Then return the election result and assign it to result, Then judge result The result is worth , If it is 1, Equal to ACCESS_GRANTED Value , It means one vote , That is to say , Permission to allow access to the interface .

Here ,ACCESS_GRANTED Consent 、ACCESS_DENIED Say no 、ACCESS_ABSTAIN To abstain from :

  1 public interface AccessDecisionVoter<S> {  2    int ACCESS_GRANTED = 1;// Consent   3    int ACCESS_ABSTAIN = 0;// To abstain from   4    int ACCESS_DENIED = -1;// Say no   5    ......  6    }

So , Under what circumstances , Voting results result For 1 Well ?

Here we need to study the voter interface AccessDecisionVoter, The implementation of the interface is shown in the following figure :

Here is a brief introduction to two commonly used :

1. RoleVoter: This is to judge url Whether the request has the role required by the interface , This method is mainly used to use annotations @Secured Permission to deal with ;
2. PreInvocationAuthorizationAdviceVoter: For similar annotations @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')") Permission to deal with ;

At this point , The code starts to be hard to understand , This part of the package is too complex , The overall logic , It is to match the permission of user information with the permission expression of the interface , If it matches successfully , return true, In the binocular operator ,

allowed ? ACCESS_GRANTED : ACCESS_DENIED, Will return ACCESS_GRANTED , That is to say, through , So , Return to result The value of is 1 了 .

Dear Jane , That's the end of the article , The author still has some shortcomings , Readers are welcome to give valuable feedback , It can also be regarded as a kind of drum for the author's writing

版权声明
本文为[itread01]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224215001771m.html

Scroll to Top