New version of Spring Security6.2 architecture (3) - Authorization

Preface

Continuing from the above, after authentication comes authorization. This article is still a structural translation and personal understanding of official website document authorization. The subsequent blog will write specific usage examples, authenticate from data, and integrate the concepts of authentication and authorization.

Authorization architecture

Authorization means that after determining the user authentication method, you also need to configure the authorization rules of the application process.

Advanced authorization features are one of the reasons why Spring Security is so trusted and popular by so many people. No matter how you choose to authenticate (whether using the mechanisms and provider processes provided by Spring Security, or integrating with a container or other non-Spring Security authentication authority), you can use the authorization service in a consistent and simple way in the application process.​ 

Consider attaching authorization rules to request URIs paths and methods. There's also a lot of detail below on how Spring Security authorization works, and how to fine-tune it once you've created the basic model.

Authorization architecture

Authorities

Authentication discusses all Authentication implementations and how to store a list of GrantedAuthority objects. These representatives have delegated authority to the principal. The GrantedAuthority object is inserted into the Authentication object by the AuthenticationManager and subsequently read by the AccessDecisionManager instance to implement authorization decisions.
The GrantedAuthority interface has only one method

String getAuthority();

AuthorizationManager instances use this method to obtain an accurate string representation of the GrantedAuthority. Most AuthorizationManager implementations can easily "read" GrantedAuthority by returning the representation as a string. If the GrantedAuthority cannot be accurately represented as a string, the GrantedAuthority is considered "complex" and getAuthority() must return null.

The complex implementation of GrantedAuthority is to store a list of actions and permission thresholds that apply to different customer accounts. It would be very difficult to represent this complex GrantedAuthority as a string. Therefore, the getAuthority() method should return null. This indicates to any AuthorizationManager that it needs to support a specific GrantedAuthority implementation in order to understand its contents.

Spring Security includes a concrete GrantedAuthority implementation: SimpleGrantedAuthority. This implementation allows any user-specified string to be converted to a GrantedAuthority. All AuthenticationProvider instances contained in the security schema use SimpleGrantedAuthority to populate Authentication objects.

By default, role-based authorization rules include ROLE_ as a prefix. This means that if there is an authorization rule that requires the security context to have the "USER" role, Spring Security will by default look for GrantedAuthority#getAuthority that returns "ROLE_USER".

You can customize this using GrantedAuthorityDefaults. GrantedAuthorityDefaults exists to allow customization of the prefixes used for role-based authorization rules.

You can configure authorization rules to use a different prefix by exposing the GrantedAuthorityDefaults bean, as follows:

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}

Here’s another tip from the official website:

GrantedAuthorityDefaults can be exposed using static methods to ensure that Spring publishes Spring Security's method security @Configuration class before it is initialized

Invocation Handling

Spring Security provides interceptors to control access to secure objects such as method calls or web requests. The pre-call decision whether to allow the call to proceed is made by the AuthorizationManager instance. Additionally, the decision as to whether a given value can be returned after the call is made by the AuthorizationManager instance.

The AuthorizationManager

AuthorizationManager replaces AccessDecisionManager and AccessDecisionVoter.​ 

Applications that use a custom AccessDecisionManager or AccessDecisionVoter are recommended to change to use an AuthorizationManager. The AuthorizationManager interface contains two methods:

AuthorizationManager is called through Spring Security's request-based, method-based and message-based authorization components and is responsible for making final access control decisions. The AuthorizationManager interface contains two methods:

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

The AuthorizationManager's check method will pass all the relevant information it needs in order to make an authorization decision. Specifically, passing a safe object enables inspection of those parameters contained in the actual call to the safe object. For example, assume the safe object is MethodInvocation. It's easy to query the MethodInvocation for any Customer parameter and then implement some security logic in the AuthorizationManager to ensure that the principal is allowed to operate on that customer. Implementations should return a positive AuthorizationDecision if access is granted, a negative AuthorizationDecision if access is denied, and an empty AuthorizationDecision if access is waived.

verify calls the check and subsequently throws an AccessDeniedException in case of a negative AuthorizationDecision.

Delegation-based AuthorizationManager implementation

While users can implement their own AuthorizationManager to control all aspects of authorization, Spring Security comes with a delegated AuthorizationManager that works with individual AuthorizationManagers.

The RequestMatcherDelegatingAuthorizationManager matches the request to the most appropriate delegating AuthorizationManager. For method safety, use AuthorizationManagerBeforeMethodInterceptor and AuthorizationManagerAfterMethodInterceptor.

Authorization Manager ImplementationsExplain its related classes.

Using this method, you can poll the AuthorizationManager on authorization decisions to implement a range of combinations.

AuthorityAuthorizationManager

The most common AuthorizationManager provided by Spring Security is AuthorityAuthorizationManager. It configures a given set of permissions to look for on the current Authentication. If Authentication contains any configured authorities, it will return a positive AuthorizationDecision. Otherwise, it returns a negative AuthorizationDecision.

AuthenticatedAuthorizationManager

Another manager is AuthenticatedAuthorizationManager. It can be used to differentiate between anonymous users, fully authenticated users, and "remember me" authenticated users. Many sites allow some limited access under "remember me" authentication, but require users to confirm their identity by logging in to gain full access.

AuthorizationManagers

There are also useful static factories in AuthenticationManagers for composing individual AuthenticationManagers into more complex expressions.

Custom Authorization Managers

Obviously you can also implement a custom AuthorizationManager and put whatever access control logic you want into it. It may be specific to your application (related to business logic) or it may implement some security management logic. For example, you can create an implementation that queries the Open Policy Agent or your own authorization database.

Tip: You can find a blog post on the Spring website that describes how to use the old AccessDecisionVoter to deny access in real time to users whose accounts have been suspended. The same result can be achieved by implementing AuthorizationManager instead.

AccessDecisionManager and AccessDecisionVoters adjustments

Before AuthorizationManager, Spring Security released AccessDecisionManager and AccessDecisionVoter.

In some cases, such as migrating older applications, it may be necessary to introduce an AuthorizationManager that calls AccessDecisionManager or AccessDecisionVoter.

To call an existing AccessDecisionManager, you can do the following:

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true);
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false);
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

Then connect it to your SecurityFilterChain.

Or, to just call AccessDecisionVoter, you can do the following:

@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
        switch (decision) {
        case ACCESS_GRANTED:
            return new AuthorizationDecision(true);
        case ACCESS_DENIED:
            return new AuthorizationDecision(false);
        }
        return null;
    }
}

Then connect it into your security filter chain.

Role Hierarchy-Hierarchical Roles

A common requirement for applications is that a specific role should automatically "contain" other roles. For example, in an application with the concept of "admin" and "user" roles, you might want the administrator to be able to do everything a normal user can do. You can do this by ensuring that all admin users are also assigned the "User" role. Alternatively, you can modify each access constraint to require that the User role also include the Administrator role. This can get very complicated if you have many different roles in your application.

Use role hierarchies to configure which roles (or permissions) should include other roles. An extended version of Spring Security's RoleVoter, RoleHierarchyVoter configures a RoleHierarchy from which it obtains all "accessible rights" assigned to a user. A typical configuration might look like this:

@Bean
static RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
            "ROLE_STAFF > ROLE_USER\n" +
            "ROLE_USER > ROLE_GUEST");
    return hierarchy;
}

// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setRoleHierarchy(roleHierarchy);
	return expressionHandler;
}

Warm reminder: RoleHierarchy Bean configuration has not been ported to @EnableMethodSecurity. Therefore, this example uses AccessDecisionVoter. If you need RoleHierarchy to support method security, please continue to use @EnableGlobalMethodSecurity until github.com/spring-projects/spring-security/issues/12783 is completed.

Here we have four roles in the hierarchy ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST. A user authenticating with ROLE_ADMIN will behave as if they have all four roles when evaluating security constraints against the AuthorizationManager applicable to the call to the RoleHierarchyVoter above. The > symbol can be thought of as meaning "includes".

Role hierarchies provide a convenient way to simplify an application's access control configuration data and/or reduce the number of permissions that need to be assigned to users. For more complex requirements, you may want to define a logical mapping between the specific access rights required by your application and the roles assigned to the user, so that you can translate between the two when loading user information.

Legacy licensing components

Spring Security contains some legacy components. The documents are included for historical purposes as they have not been deleted. Their recommended alternatives are listed above.

The AccessDecisionManager

AccessDecisionManager is called by AbstractSecurityInterceptor and is responsible for making final access control decisions. The AccessDecisionManager interface contains three methods:

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManager's decide method passes all the relevant information needed to make an authorization decision. Specifically, passing a safe object enables inspection of those parameters contained in the actual call to the safe object. For example, assume the safe object is MethodInvocation. You can query any Customer parameter in the MethodInvocation and then implement some security logic in the AccessDecisionManager to ensure that the principal is allowed to operate on that customer. If access is denied, implementations should throw AccessDeniedException.

The supports(ConfigAttribute) method is called by the AbstractSecurityInterceptor on startup to determine whether the AccessDecisionManager can handle the passed ConfigAttribute. The supports(Class) method is called by the security listener implementation to ensure that the configured AccessDecisionManager supports the security object type provided by the security listener.

Voting-based AccessDecisionManager implementation

While users can implement their own AccessDecisionManager to control all aspects of authorization, Spring Security includes multiple voting-based AccessDecisionManager implementations. The voting decision manager describes the related classes.

The following figure shows the AccessDecisionManager interface:

Using this method, a series of AccessDecisionVoter implementations are polled for authorization decisions. The AccessDecisionManager then decides whether to throw an AccessDeniedException based on its evaluation of the vote.

The AccessDecisionVoter interface has three methods:

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

The implementation returns an int and the possible values ​​are reflected in the AccessDecisionVoter static fields named ACCESS_ABSTAIN, ACCESS_DENIED and ACCESS_GRANTED. If the voting implementation has no objections to the authorization decision, ACCESS_ABSTAIN is returned. If it does have objections, it must return ACCESS_DENIED or ACCESS_GRANTED.

Spring Security provides three specific AccessDecisionManager implementations to count votes. ConsensusBased implements granting or denying access based on consensus with non-abstention votes. Provides properties to control behavior in the event of a tie or all votes abstaining. If one or more ACCESS_GRANTED votes are received, the AffirmativeBased implementation will grant access (in other words, if there is at least one grant vote, the deny vote will be ignored). As with the ConsensusBased implementation, there is a parameter that controls the behavior if all voters abstain. The UnanimousBased provider expects a consistent ACCESS_GRANTED vote to grant access and ignores abstention votes. If there are any ACCESS_DENIED votes, it will deny access. As with other implementations, there is a parameter that controls the behavior when all voters abstain.

You can implement a custom AccessDecisionManager that counts votes differently. For example, votes from a specific AccessDecisionVoter may receive additional weight, while rejection votes from a specific voter may have a veto effect.

RoleVoter

The most commonly used AccessDecisionVoter provided by Spring Security is the RoleVoter, which treats configuration properties as role names and votes to grant access when a user is assigned that role.

If any ConfigAttribute starts with ROLE_ prefix, it will vote. If there is a GrantedAuthority that returns a String representation (from the getAuthority() method) that is exactly equal to one or more ConfigAttributes starting with the ROLE_ prefix, it votes to grant access. If any ConfigAttribute starting with ROLE_ does not have an exact match, the RoleVoter will vote to deny access. If no ConfigAttribute begins with ROLE_, the voter abstains.

AuthenticatedVoter

Another voter we see implicitly is the AuthenticatedVoter, which can be used to differentiate between anonymous, fully authenticated, and remember-me authenticated users. Many sites allow some limited access under "remember me" authentication, but require users to confirm their identity by logging in to gain full access.

When we grant anonymous access using the IS_AUTHENTICATED_ANONYMOUSLY attribute, this attribute is being handled by the AuthenticatedVoter. For more information, see AuthenticatedVoter.

Custom VotersCustom Voters

You can also implement a custom AccessDecisionVoter and put any access control logic you want in it. It may be specific to your application (related to business logic) or it may implement some security management logic. For example, on the Spring website you can find a blog post that explains how to use voters to deny access in real time to users whose accounts have been suspended.

Like many other parts of Spring Security, AfterInvocationManager has a concrete implementation, the AfterInvocationProviderManager, which polls a list of AfterInvocationProviders. Each AfterInvocationProvider is allowed to modify the return object or throw an AccessDeniedException. In fact, multiple providers can modify the object because the results of the previous provider are passed to the next provider in the list.

Note that if you are using AfterInvocationManager, you still need a configuration property that allows the MethodSecurityInterceptor's AccessDecisionManager to allow operations. If you are using the typical AccessDecisionManager implementation included with Spring Security, not defining a configuration property for a specific security method call will cause each AccessDecisionVoter to be discarded. Conversely, if the AccessDecisionManager property "allowIfAllAbstainDecisions" is false, an AccessDeniedException will be thrown. You can avoid this potential problem by either: (i) setting "allowIfAllAbstainDecisions" to true (although this is generally not recommended) or (ii) simply ensuring that the AccessDecisionVoter votes to grant access to at least one configuration property. The latter (recommended) method is usually implemented via the ROLE_USER or ROLE_AUTHENTICATED configuration property.

references:

"Spring bott official website"

Guess you like

Origin blog.csdn.net/u012895183/article/details/134923916