Spring Security Authorization Architecture

Table of contents

1. Authorities authorization (AuthorizationFilter filter)

2. Authorization Manager Authorization Manager

3. Hierarchy of roles (Roles)


1. Authorities authorization ( AuthorizationFilter filter)

The authenticated user authorization information ( GrantedAuthority ) can be obtained         through Authentication.getAuthorities() . The GrantedAuthority  object  is inserted into the Authentication object by the AuthenticationManager  , and then read by the AuthorizationManager instance when making an authorization decision .

The common implementation of         AuthenticationManager  is ProviderManager. ProviderManager.authenticate will populate the Authentication object after the authentication is completed  , including the authorization information of the object ( GrantedAuthority ).

//具体授权的Provider列表
private List<AuthenticationProvider> providers;

//授权方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //省略...

        //1-定义身份验证对象
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        Iterator var9 = this.getProviders().iterator();

        while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {
                //省略...

                try {

                    //2-通过用户验证后,在这里返回填充好的Authentication对象
                    result = provider.authenticate(authentication);

                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        //省略...

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }
            //3-在这里将封装的对象返回
            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }
            throw lastException;
        }
    }

        ​​​​There are many implementations of AuthenticationProvider. Here we use the commonly used AbstractUserDetailsAuthenticationProvider to see the filling steps of user permissions.

    //AbstractUserDetailsAuthenticationProvider.authenticate方法
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        //省略...

        //1-获取用户名称
        String username = this.determineUsername(authentication);

        //2-从缓存中获取用户信息(UserDetails)
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                //3-缓存中无用户信息,再去查找其他UserDetails的实现
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                //错误处理,省略...
        }

        //省略..

        //4-最后这个返回,就是返回封装好的Authentication对象
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    //封装Authentication对象
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {

        //在这里设置了用户的权限->user.getAuthorities() -> GrantedAuthority
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));

        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");
        return result;
    }

        After the user permissions are filled, when will they be used? Permission information is used by the authorization filter  AuthorizationFilter when authorizing.

//授权过滤器
public class AuthorizationFilter extends OncePerRequestFilter {

    //授权管理器
    private final AuthorizationManager<HttpServletRequest> authorizationManager;

    public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
        Assert.notNull(authorizationManager, "authorizationManager cannot be null");
        this.authorizationManager = authorizationManager;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // verify方法进行授权验证,this::getAuthentication 获取权限信息
        this.authorizationManager.verify(this::getAuthentication, request);

        filterChain.doFilter(request, response);
    }

    private Authentication getAuthentication() {
        //从SecurityContextHolder中获取权限信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
        } else {
            return authentication;
        }
    }
}

2. Authorization Manager Authorization Manager

        An AuthorizationManager , used to determine whether an Authentication is authorized to access a particular object.

AuthorizationManager replaces AccessDecisionManager and AccessDecisionVoter         in Spring Security .

        Spring officially encourages applications that customize AccessDecisionManager or AccessDecisionVoter to use AuthorizationManager instead .

        AuthorizationManager  is invoked by AuthorizationFilter and is responsible for making final access control decisions. The AuthorizationManager interface contains two methods: //functional interface

@FunctionalInterface
public interface AuthorizationManager<T> {

    //确定是否为特定Authentication或对象授予访问权限。
    default void verify(Supplier<Authentication> authentication, T object) {
        AuthorizationDecision decision = this.check(authentication, object);
        if (decision != null && !decision.isGranted()) {
            throw new AccessDeniedException("Access Denied");
        }
    }

    //确定是否为特定Authentication或对象授予访问权限。
    @Nullable
    AuthorizationDecision check(Supplier<Authentication> var1, T var2);
}

        Users can customize authorization control by implementing the AuthorizationManager interface. At the same time, Spring Security comes with a delegated AuthorizationManager, which can cooperate with various AuthorizationManagers.

        RequestMatcherDelegatingAuthorizationManager will choose the most appropriate AuthorizationManager to match the request. For method security control, you can use AuthorizationManagerBeforeMethodInterceptor and AuthorizationManagerAfterMethodInterceptor to implement. // Note that these two interceptors are only available in versions after 5.6

    //请求匹配器和授权管理器的集合
    private final Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings;  
  
    //RequestMatcherDelegatingAuthorizationManager.check方法
    public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Authorizing %s", request));
        }
        //1-获取请求匹配器和授权管理器集合的迭代器
        Iterator var3 = this.mappings.entrySet().iterator();

        Entry mapping;
        MatchResult matchResult;
        do {
            if (!var3.hasNext()) {
                this.logger.trace("Abstaining since did not find matching RequestMatcher");
                return null;
            }
            //2-获取一个Entry
            mapping = (Entry)var3.next();
            //3-获取一个RequestMatcher 
            RequestMatcher matcher = (RequestMatcher)mapping.getKey();
            //4-进行匹配
            matchResult = matcher.matcher(request);
        } while(!matchResult.isMatch());

        //5-如果匹配成功,会选择一个对应的授权器:AuthorizationManager
        AuthorizationManager<RequestAuthorizationContext> manager = (AuthorizationManager)mapping.getValue();
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
        }

        //6-选择特定的授权管理器进行授权操作
        return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables()));
    }

        AuthorizationManagerBeforeMethodInterceptor : is a method interceptor that uses AuthorizationManager to determine whether an Authentication can call a given MethodInvocation. //The interceptor intercepts before the method call

    //授权管理器
    private final AuthorizationManager<MethodInvocation> authorizationManager;    
    
    //AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization方法
    //在该拦截器中尝试使用授权管理器进行授权
    private void attemptAuthorization(MethodInvocation mi) {
        this.logger.debug(LogMessage.of(() -> {
            return "Authorizing method invocation " + mi;
        }));
        //1-执行授权操作
        AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);

        //2-发布授权事件(Authentication对象,调用方法,授权结果)
        this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);

        if (decision != null && !decision.isGranted()) {

            //3-授权失败,记录日志,抛出异常
            this.logger.debug(LogMessage.of(() -> {
                return "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision;
            }));
            throw new AccessDeniedException("Access Denied");
        } else {

             //4-授权成功,记录日志,后续执行指定方法
            this.logger.debug(LogMessage.of(() -> {
                return "Authorized method invocation " + mi;
            }));
        }
    }

    //AuthorizationManagerBeforeMethodInterceptor.invoke方法
    public Object invoke(MethodInvocation mi) throws Throwable {
        //1-调用拦截器进行授权
        this.attemptAuthorization(mi);

        //2-授权成功后执行指定方法(此时授权没有抛出异常)
        return mi.proceed();
    }

        For the corresponding authorization manager, Spring Security provides the designated authorizer  PreAuthorizeAuthorizationManager , which performs authorization-related operations in the authorizer.

        AuthorizationManagerAfterMethodInterceptor : Also a MethodInterceptor, it can use AuthorizationManager to determine whether an Authentication can access the result of a MethodInvocation. //This interceptor intercepts after the method call ends

The implementation class diagram of AuthorizationManager         :

3. Hierarchy of roles (Roles)

        Trying to specify a specific role in the application will automatically "include" other roles. For example, in an application with the concept of "Admin" and "User" roles, it is expected that an administrator ("Admin") can perform all actions of a normal user ("User"). To do this, make sure that all administrator roles are also assigned the "User" role.

        A role hierarchy allows configuring a role (or permission) to include other roles . An extended version of Spring Security's RoleVoter , the RoleHierarchyVoter configures a RoleHierarchy from which it derives all "accessibility rights" assigned to a user. //role selector

        A typical configuration might look like this:

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

        Four hierarchical roles are defined in the code above:

ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST

        Users who use the ROLE_ADMIN role for authentication will also have the permissions of other roles , where ">" can be regarded as "include".

        Note: RoleHierarchy bean configuration has not been migrated to @EnableMethodSecurity. Therefore, this example uses AccessDecisionVoter. If you need RoleHierarchy to support method security, please continue to use @EnableGlobalMethodSecurity until this bug is fixed. //This configuration has a bug, and it should be able to be used normally in the future

        By the way, Spring Security also provides a way for AccessDecisionManager and AccessDecisionVoters to adapt AuthorizationManager to be compatible with the previous code, please click here .

        So far, the authorization framework of Spring Security has been introduced. In this article, it is more about methodology, and the specific usage details will be supplemented in subsequent articles.

Guess you like

Origin blog.csdn.net/swadian2008/article/details/131914330