Today let’s talk about the voting mechanism and voting machine in Spring Security.

When a user wants to access a protected resource in Spring Security, the user has some roles and access to the resource requires some roles. The voting mechanism and voting mechanism are used to compare the roles that the user has and the roles that the resource requires.

When a user wants to access a certain resource, the voting machine votes for or against the user according to the user’s role, and the voting method is based on the results of the voting machine.

In Spring Security, there are three voting mechanisms provided by default, but it is also possible to define your own voting mechanism without using the system’s voting mechanism and voting machine.

In this article, Songo will focus on the three voting mechanisms and the default voting machine.

1. The vote

Let’s start with the voting machines.

In Spring Security, the voter is regulated by the AccessDecisionVoter interface. Let’s see the implementation of the AccessDecisionVoter interface:

As you can see, there are many voter implementations. You can choose one or more of them or customize them. The default voter is WebExpressionVoter.

The definition of AccessDecisionVoter is:

public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class
        clazz);
	int vote(Authentication authentication, S object, Collection
       
         attributes)
       ;
}
Copy the code

Let me explain a little bit:

  1. First of all, three constants are defined. The meaning of each constant can be seen from the name of the constant. 0 indicates abstention; -1 indicates rejection.
  2. Two supports methods are used to determine whether the voter supports the current request.
  3. At the end of the day we can vote. Implemented in different implementation classes. Three parameters: authentication indicates the current login principal. The Object is an ilterInvocation that encapsulates the current request; Attributes represents the set of roles required by the interface currently accessed.

Let’s take a look at the implementation of several voting machines.

1.1 RoleVoter

RoleVoter is used to check whether the current request has the required role for this interface.

public int vote(Authentication authentication, Object object, Collection
       
         attributes)
        {
	if (authentication == null) {
		return ACCESS_DENIED;
	}
	int result = ACCESS_ABSTAIN;
	Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
	for (ConfigAttribute attribute : attributes) {
		if (this.supports(attribute)) {
			result = ACCESS_DENIED;
			for (GrantedAuthority authority : authorities) {
				if (attribute.getAttribute().equals(authority.getAuthority())) {
					returnACCESS_GRANTED; }}}}return result;
}
Copy the code

If the current login principal is null, ACCESS_DENIED means access is denied. Otherwise, the role information is extracted from the current login principal Authentication and compared to attributes. If attributes have any of the required roles, ACCESS_GRANTED is returned to allow access. For example, if the role in the Attributes field is [a,b, C] and the current user has A, access is allowed.

Another important note to note is the supports method of RoleVoter.

public class RoleVoter implements AccessDecisionVoter<Object> {
	private String rolePrefix = "ROLE_";
	public String getRolePrefix(a) {
		return rolePrefix;
	}
	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}
	public boolean supports(ConfigAttribute attribute) {
		if((attribute.getAttribute() ! =null)
				&& attribute.getAttribute().startsWith(getRolePrefix())) {
			return true;
		}
		else {
			return false; }}public boolean supports(Class
        clazz) {
		return true; }}Copy the code

As you can see, there is a rolePrefix involved, which is ROLE_. In the supports method, the supoorts method will return true only if the principal rolePrefix is ROLE_.

1.2 RoleHierarchyVoter

RoleHierarchyVoter is a subclass of RoleVoter that introduces role hierarchymanagement, also known as role inheritance, on the basis of RoleVoter’s role judgment. You can refer to Songo’s previous article (how to make superiors have all permissions of subordinates in Spring Security?). .

The Vote method of the RoleHierarchyVoter class is identical to that of RoleVoter, with the only difference being that the RoleHierarchyVoter class overrides the Extractor orities method.

@Override
Collection<? extends GrantedAuthority> extractAuthorities(
		Authentication authentication) {
	return roleHierarchy.getReachableGrantedAuthorities(authentication
			.getAuthorities());
}
Copy the code

Role after stratification, need through getReachableGrantedAuthorities method to get the actual role, specific please refer to: how to make the superior in the Spring Security with lower all permissions? The article.

1.3 WebExpressionVoter

Votes-based permission control: votes-based permission control: votes-based permission control: votes-based permission control: votes-based permission control: votes-based permission control: votes-based permission control

public int vote(Authentication authentication, FilterInvocation fi, Collection
       
         attributes)
        {
	assertauthentication ! =null;
	assertfi ! =null;
	assertattributes ! =null;
	WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
	if (weca == null) {
		return ACCESS_ABSTAIN;
	}
	EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
			fi);
	ctx = weca.postProcess(ctx, fi);
	return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
			: ACCESS_DENIED;
}
Copy the code

If you are familiar with SpEL, this code, it should be said or well understood, but in my experience, used in practical work despite SpEL scene, but not much, so there may be a lot of friend does not understand the usage of SpEL, this need friends to review, I also give you recommend a good essay: www.cnblogs.com/larryzeal/p… .

Here the code essentially builds the wecA object based on the attributes attribute passed in, then builds the CTX object based on the authentication parameter passed in, and finally calls the evaluateAsBoolean method to determine if the permissions match.

The three voting machines described above are the ones we use most in our actual development.

1.4 other

In addition, there are a few more unpopular voting machines, Songko also said a little, friends understand.

Jsr250Voter

Voting machines that handle JSR-250 permission annotations, such as @permitall, @denyall, etc.

AuthenticatedVoter

AuthenticatedVoter Is used to check whether the configuration attribute is enabled IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_ANONYMOUSLY

IS_AUTHENTICATED_FULLY indicates that the current user must be authenticated by username/password. Authentication through a RememberMe is not valid.

IS_AUTHENTICATED_REMEMBERED indicates that the current logged-in user must be authenticated through a RememberMe.

IS_AUTHENTICATED_ANONYMOUSLY Indicates that the current login user must be an anonymous user.

Consider this when your project introduces a RememberMe and you want to distinguish between different authentication methods.

AbstractAclVoter

Provides help methods for writing ACL options for domain objects that are not tied to any particular ACL system.

PreInvocationAuthorizationAdviceVoter

Using the @ PreFilter and @ PreAuthorize annotation processing permissions, authorized by PreInvocationAuthorizationAdvice.

Of course, if these voting machines do not meet the requirements, you can also customize.

2. Voting mechanism

A request may not only have one voting machine, but also may have multiple voting machines, so we need a voting mechanism on the basis of voting machines.

Voting related classes are mainly three:

  • AffirmativeBased
  • ConsensusBased
  • UnanimousBased

Their inheritance is shown above.

The three decision makers will call all the vote makers in the project. The default decision maker is AffirmativeBased.

The differences between the three decision makers are as follows:

  • AffirmativeBased: One of the voting machines agree and pass.
  • ConsensusBased: majority vote is agreed to by, a draw, see allowIfEqualGrantedDeniedDecisions parameter values.
  • Both bureaus agree, then the request passes.

Here the specific judgment logic is relatively simple, Songge will not paste the source code, interested partners can have a look.

3. Where do I configure it

When we use express-based permission control, it looks like this:

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().fullyAuthenticated()
Copy the code

The default of voting and decision making is in AbstractInterceptUrlConfigurer# createDefaultAccessDecisionManager method of configuration:

private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
	AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
	returnpostProcess(result); } List<AccessDecisionVoter<? >> getDecisionVoters(H http) { List<AccessDecisionVoter<? >> decisionVoters =new ArrayList<>();
	WebExpressionVoter expressionVoter = new WebExpressionVoter();
	expressionVoter.setExpressionHandler(getExpressionHandler(http));
	decisionVoters.add(expressionVoter);
	return decisionVoters;
}
Copy the code

The main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows: the main Settings are as follows:

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().fullyAuthenticated()
        .withObjectPostProcessor(new ObjectPostProcessor<AffirmativeBased>() {
            @Override
            public <O extends AffirmativeBased> O postProcess(O object) { List<AccessDecisionVoter<? >> decisionVoters =new ArrayList<>();
                decisionVoters.add(new RoleHierarchyVoter(roleHierarchy()));
                AffirmativeBased affirmativeBased = new AffirmativeBased(decisionVoters);
                return (O) affirmativeBased;
            }
        })
        .and()
        .csrf()
        .disable();
Copy the code

This is just a demo, normally we don’t need to do this. When we use different permission configuration methods, there will be automatic configuration corresponding to the voter and decision. Or we can manually configure the voters and decision-makers. If they are configured by the system, in most cases we don’t need to change them.

4. Summary

In this article, I will briefly share with you the voting machines and decision makers in Spring Security. In the next article, I will continue to talk about authorization in detail.