English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Discussion on Spring Security Permission Management (Source Code Analysis)

Recently, the project needs to use the permission control of Spring Security, so I spent some time looking at the source code related to its permission control (version is4.2)。

AccessDecisionManager

Spring Security is managed by AccessDecisionManager for authorization, here's an official image to hold the floor.

AccessDecisionManager

The AccessDecisionManager interface defines the following methods:

//Call AccessDecisionVoter to vote (a key method)
void decide(Authentication authentication, Object object,
    Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
    InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);

Next, let's look at the specific implementation of its implementation class:

AffirmativeBased

public void decide(Authentication authentication, Object object,
    Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
  int deny = 0;
  for (AccessDecisionVoter voter : getDecisionVoters()) {
    //Call AccessDecisionVoter to vote (let's call it voting for the sake of convenience), and we will look at the source code of vote later.
    int result = voter.vote(authentication, object, configAttributes);
    if (logger.isDebugEnabled()) {
      logger.debug("Voter: " + voter + ", returned: " + result);
    }
    switch (result) {
    case AccessDecisionVoter.ACCESS_GRANTED://The value is1
      //If the voter votes for ACCESS_GRANTED, it is approved
      
    case AccessDecisionVoter.ACCESS_DENIED://The value is-1
      deny++;
      break;
    default:
      break;
    }
  }
  if (deny > 0) {
    //If two or more AccessDecisionVoter (let's call them voters for the sake of convenience) cast ACCESS_DENIED, it is directly not approved
    throw new AccessDeniedException(messages.getMessage(
        "AbstractAccessDecisionManager.accessDenied", "Access is denied");
  }
  // To get this far, every AccessDecisionVoter abstained
  checkAllowIfAllAbstainDecisions();
}

From the above code, we can directly see the strategy of AffirmativeBased:

  • If there is at least one vote in favor (ACCESS_GRANTED), it is directly deemed to be approved.
  • If there are two or more votes against (ACCESS_DENIED) and no votes in favor, it is directly deemed to be disapproved.

UnanimousBased

public void decide(Authentication authentication, Object object,
    Collection<ConfigAttribute> attributes) throws AccessDeniedException {
  int grant = 0;
  int abstain = 0;
  List<ConfigAttribute> singleAttributeList = new ArrayList<ConfigAttribute>(1);
  singleAttributeList.add(null);
  for (ConfigAttribute attribute : attributes) {
    singleAttributeList.set(0, attribute);
    for (AccessDecisionVoter voter : getDecisionVoters()) {
      //The configured voter casts a vote
      int result = voter.vote(authentication, object, singleAttributeList);
      if (logger.isDebugEnabled()) {
        logger.debug("Voter: " + voter + ", returned: " + result);
      }
      switch (result) {
      case AccessDecisionVoter.ACCESS_GRANTED:
        grant++;
        break;
      case AccessDecisionVoter.ACCESS_DENIED:
        //If there is a voter casting an opposing vote, it is immediately deemed to have no access rights.
        throw new AccessDeniedException(messages.getMessage(
            "AbstractAccessDecisionManager.accessDenied",
            "Access is denied");
      default:
        abstain++;
        break;
      }
    }
  }
  // To get this far, there were no deny votes
  if (grant > 0) {
    //If there are no dissenting votes and there are votes in favor, it is judged as passed
    
  }
  // To get this far, every AccessDecisionVoter abstained
  checkAllowIfAllAbstainDecisions();
}

Therefore, the strategy of UnanimousBased is as follows:

  • No matter how many voters vote how many ACCESS_GRANTED votes, as long as there is a dissenting vote (ACCESS_DENIED), it is judged as not passed.
  • If there are no dissenting votes and a voter votes in favor, it is judged as passed.

ConsensusBased

public void decide(Authentication authentication, Object object,
    Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
  int grant = 0;
  int deny = 0;
  int abstain = 0;
  for (AccessDecisionVoter voter : getDecisionVoters()) {
    //The configured voter casts a vote
    int result = voter.vote(authentication, object, configAttributes);
    if (logger.isDebugEnabled()) {
      logger.debug("Voter: " + voter + ", returned: " + result);
    }
    switch (result) {
    case AccessDecisionVoter.ACCESS_GRANTED:
      grant++;
      break;
    case AccessDecisionVoter.ACCESS_DENIED:
      deny++;
      break;
    default:
      abstain++;
      break;
    }
  }
  if (grant > deny) {
    //If the number of votes in favor is greater than the number of votes against, it is judged as passed
    
  }
  if (deny > grant) {
    //If the number of votes in favor is less than the number of votes against, it is judged as not passed
    throw new AccessDeniedException(messages.getMessage(
        "AbstractAccessDecisionManager.accessDenied", "Access is denied");
  }
  if ((grant == deny) && (grant != 0)) {
    //this.allowIfEqualGrantedDeniedDecisions is default true
    //If the number of votes in favor is equal to the number of votes against, it can be judged whether to pass according to the configuration allowIfEqualGrantedDeniedDecisions.
    if (this.allowIfEqualGrantedDeniedDecisions) {
      
    }
    else {
      throw new AccessDeniedException(messages.getMessage(
          "AbstractAccessDecisionManager.accessDenied", "Access is denied");
    }
  }
  // To get this far, every AccessDecisionVoter abstained
  checkAllowIfAllAbstainDecisions();
}

From this, we can see that the strategy of ConsensusBased:

  • If the number of votes in favor is greater than the number of votes against, it is judged as passed.
  • If the number of votes in favor is less than the number of votes against, it is judged as not passed.
  • If the number of votes in favor is equal to the number of votes against, it can be judged whether to pass according to the configuration allowIfEqualGrantedDeniedDecisions (default is true).

By now, you should understand the differences between AffirmativeBased, UnanimousBased, and ConsensusBased. Spring Security defaults to using AffirmativeBased, and if needed, it can be configured to use the other two, or you can implement it yourself.

Voter

All the implementation classes of AccessDecisionManager mentioned above are only for managing permissions (voting) (the implementation of strategies), and the specific voting (vote) logic is implemented by calling the vote method of the subclass (voter) of AccessDecisionVoter. Spring Security defaults to registering two voters: RoleVoter and AuthenticatedVoter. Let's take a look at its source code below.

AccessDecisionManager

boolean supports(ConfigAttribute attribute);
boolean supports(Class<63;> clazz);
//Core method, this method is called by the AccessDecisionManager introduced above, and subclasses implement this method to vote.
int vote(Authentication authentication, S object,)
    Collection<ConfigAttribute> attributes);}}

RoleVoter

private String rolePrefix = "ROLE_";
//Only handle those starting with ROLE_ (the value of rolePrefix can be changed through configuration)
public boolean supports(ConfigAttribute attribute) {
  if ((attribute.getAttribute() != null)
      && attribute.getAttribute().startsWith(getRolePrefix())) {
    return true;
  }
  else {
    return false;
  }
}
public int vote(Authentication authentication, Object object,
    Collection<ConfigAttribute> attributes) {
  if(authentication == null) {
    //Vote against if the user has not passed authentication
    return ACCESS_DENIED;
  }
  int result = ACCESS_ABSTAIN;
  //Obtain the actual permissions of the user
  Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
  for (ConfigAttribute attribute : attributes) {
    if (this.supports(attribute)) {
      result = ACCESS_DENIED;
      // Attempt to find a matching granted authority
      for (GrantedAuthority authority : authorities) {
        if (attribute.getAttribute().equals(authority.getAuthority())) {
          //Vote in favor if the permissions match
          return ACCESS_GRANTED;
        }
      }
    }
  }
  //If processed but not voted in favor, it is considered an opposing vote; if not processed, it is regarded as a abstention (ACCESS_ABSTAIN).
  return result;
}  

It's simple, at the same time, we can also extend our voter by implementing AccessDecisionManager. However, to implement this, we must also understand where the attributes parameter comes from, which is a very critical parameter. An official diagram can clearly show this problem:

Next, let's take a look at the caller of AccessDecisionManager, AbstractSecurityInterceptor.

AbstractSecurityInterceptor

...
//As mentioned above, it is default AffirmativeBased, which can be configured
private AccessDecisionManager accessDecisionManager;
...
protected InterceptorStatusToken beforeInvocation(Object object) {
  ...
  //Abstract method, implemented by subclasses, but it can also be seen that ConfigAttribute is obtained from SecurityMetadataSource (actually, it is DefaultFilterInvocationSecurityMetadataSource by default).
  Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
      .getAttributes(object);
  ...
  //Get current authenticated user information
  Authentication authenticated = authenticateIfRequired();
  try {
    //Call AccessDecisionManager
    this.accessDecisionManager.decide(authenticated, object, attributes);
  }
  catch (AccessDeniedException accessDeniedException) {
    publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
        accessDeniedException));
    throw accessDeniedException;
  }
  ...   
}
public abstract SecurityMetadataSource obtainSecurityMetadataSource();

The above methods are all called by the subclasses of AbstractSecurityInterceptor (by default, FilterSecurityInterceptor), let's take a look:

FilterSecurityInterceptor

...
//The implementation class of SecurityMetadataSource, as can be seen, can be configured externally. This also indicates that we can extend our actual needed ConfigAttribute by customizing the implementation class of SecurityMetadataSource
private FilterInvocationSecurityMetadataSource securityMetadataSource; 
...
//entry point
public void doFilter(ServletRequest request, ServletResponse,
    FilterChain chain) throws IOException, ServletException {
  FilterInvocation fi = new FilterInvocation(request, response, chain);
  //key method
  invoke(fi);
}
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 {
    // This is the first time this request is being called, so perform security checking
    if (fi.getRequest() != null) {
      fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    }
    //Here, the method of the parent class (AbstractSecurityInterceptor) is called, which also calls accessDecisionManager
    InterceptorStatusToken token = super.beforeInvocation(fi);
    try {
      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    }
    finally {
      super.finallyInvocation(token);
    }
    //Then execute the (parent class method), one after another, AOP is everywhere
    super.afterInvocation(token, null);
  }
}

Alright, now you should be quite clear about the permission management of Spring Security. After reading this, do you think you can expand a set of permissions that suits your needs? If you're still not clear, that's fine too. The next article will be a practical example, and we will develop a set of our own permission systems based on it.

That's all for this article. I hope it will be helpful to your studies, and I also hope everyone will support the Yelling Tutorial.

Declaration: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been edited by humans, and does not assume relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#w3Please report via email to codebox.com (replace # with @ when sending an email) and provide relevant evidence. Once verified, this site will immediately delete the content suspected of infringement.