编程知识 cdmana.com

Deeply understand the principle of spring security authorization mechanism

original / Zhu Jiqian

stay Spring Security In the authority framework , To update the backend http The interface implements the authorization control , There are two ways to do it .

One 、 One is based on annotation method level authentication , among , There are also 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 、 Based on the config Configuration class , Need to be corresponding to config Class configuration @EnableGlobalMethodSecurity(prePostEnabled = true) The annotation will take effect , Its authority control mode 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             // The setting request must be authenticated 
  6             .authorizeRequests()
  7             // Home page and landing page 
  8             .antMatchers("/").permitAll()
  9             .antMatchers("/login").permitAll()
 10             //  All other requests require authentication 
 11             .anyRequest().authenticated();
 12     // Log out processing 
 13     httpSecurity.logout().logoutSuccessHandler(...);
 14     //token Verify filter 
 15     httpSecurity.addFilterBefore(...);
 16 }

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

So here comes the question , We configure these annotations or classes , Its security Box is how to help achieve specific back-end API Do the interface control ?

Just from one line @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')") From the notes , I can't see any clue , To answer this question , We need to go deep into the source code level , That's right security The authorization mechanism has a better understanding .

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 simple sentences that its overall realization , Take the 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 so url The request must be set in the configuration class for permission authentication , Will be security Frame usage filter The interceptor intercepts and authenticates the request . The interception process is mainly an action , Is to combine the permission set of the request with @PreAuthorize Set the permission character of “sys:user:add” Match , If it matches , Indicates that the request has a call “/save” Interface permissions , that , Can be allowed to execute the interface resource .

 

stay springboot+security+jwt In the frame , Or through a series of built-in filters Filter To achieve access control , How to set a custom filter Filter Well ? for example , Can be set by httpSecurity.addFilterBefore(new JwtFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) To customize a system based on JWT Intercepting filters JwtFilter, there addFilterBefore Methods will be analyzed in detail in the next article , I'm not going to expand it here , This method roughly means , Custom filters will JwtFilter Add to Security In frame , Become one of the security priorities Filter, The code level is to add a custom filter to List<Filter> filters.

 

Set to add a self-defined filter Filter The pseudocode 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                 // The setting request must be authenticated 
 11                 .authorizeRequests()
 12                 ......
 13                 // Home page and landing page 
 14                 .antMatchers("/").permitAll()
 15                 .antMatchers("/login").permitAll()
 16                 //  All other requests require authentication 
 17                 .anyRequest().authenticated();
 18         ......
 19         //token Verify 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 need to be repeated . After this configuration , When asked to come over , Will be automatically JwtFilter Class interception , At this time , Will perform the overridden doFilterInternal Method , stay SecurityContextHolder.getContext().setAuthentication(authentication) After 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        //  obtain token,  And check the login 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 }

that , The problem is coming. , Filter chain FilterChain What is it ?

here , First click in to see its 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 :

image

The filter chain is like an iron chain , Link the relevant filters together , Request threads are like ants , It's going to climb all the way down this chain ----- namely , adopt chain.doFilter(request, response) Method , Layer by layer, layer by layer , 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 Do the processing , And the filters don't interfere with each other .

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

 

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

 

Can be in DefaultSecurityFilterChain Class depends on the output log perhaps debug Check it out. Security What are the filters , If in DefaultSecurityFilterChain Break point in constructor in class , As shown in the figure , You can see , Self defined JwtFilter The filter also includes :

image

These filters are all in the same filter chain , That is, through chain.doFilter(request, response) You can forward requests 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 permission information ;

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

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

  

We will start from FilterSecurityInterceptor Here we start to focus on 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 , The last filter is passed to filter request,response,chain Copy and save to FilterInvocation in , Dedicated to FilterSecurityInterceptor Filter use . What's interesting about it is , It is to unify multiple parameters into one class , It plays the role of unified management , Would you , if N Multiple parameters , They come in and they're scattered all over the class , There are many parameters , There's a lot of code , When the methods are too scattered , It may easily lead to the reading process , Get confused where these parameters come from . But if it's unified into a class , We can quickly locate the source , Easy to read code . Someone on the Internet mentioned that FilterInvocation Classes also decouple , That is, avoid using the same reference variables as other filters .

To make a long story short , 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 Method call 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 has been 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. Subsequent processing 
 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 To realize ,beforeInvocation(Object object) The implementation of the system mainly includes the following steps :

 

One 、 Access to the required interface permissions , here debug An example of this is to call the above mentioned “/save” Interface , Its permission setting is @PreAuthorize("hasAuthority('sys:user:add') AND hasAuthority('sys:user:edit')"), According to the screenshot below , You know the variables attributes Access to the request interface has been obtained :

image

Two 、 Get the certification and save it in SecurityContextHolder User information for , among ,authorities It is a collection of all the permissions that a user has ;

image

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 , perform SecurityContextHolder.getContext().setAuthentication(authentication) Save user information in Security In the frame , After that, you can go through SecurityContextHolder.getContext().getAuthentication() Get the saved user information ;

 

3、 ... and 、 Try to authorize , User information authenticated、 Request to carry object information object、 Permission information of the interface accessed attributes, The incoming to decide Method ;

image

decide() It's decision Manager AccessDecisionManager A way of defining .

  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 you can pass , The corresponding interface can be accessed .

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

image

AffirmativeBased By one vote , This is a AccessDecisionManager Default class ;

ConsensusBased The minority is subordinate to the majority ;

UnanimousBased One vote against ;

How to understand this voting mechanism ?

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

image

there 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, And then determine result Result value , if 1, be equal to ACCESS_GRANTED When the value of , It means one vote , That is to say , Permission to access the interface .

here ,ACCESS_GRANTED Consent 、ACCESS_DENIED To refuse 、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;// To refuse 
  5    ......
  6    }

that , Under what circumstances , Voting results result by 1 Well ?

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

image

Here is a brief introduction to two commonly used :

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

image

To this step , The code starts to get 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 a three letter operator ,

allowed ? ACCESS_GRANTED : ACCESS_DENIED, It will return ACCESS_GRANTED , It means to pass , such , Return to result The value is 1 了 .

image

image

Only this and nothing more , This is the end of the article , The author still has some shortcomings , Readers are welcome to give valuable feedback , It can also be regarded as an encouragement to the author's writing .

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

Scroll to Top