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