Spring Security 授权体系结构

目录

[1、Authorities 授权(AuthorizationFilter 过滤器)](#1、Authorities 授权(AuthorizationFilter 过滤器))

[2、AuthorizationManager 授权管理器](#2、AuthorizationManager 授权管理器)

3、角色的层次化(Roles)


1、Authorities 授权(AuthorizationFilter过滤器)

通过 Authentication.getAuthorities() 可获取已通过验证的用户授权信息(GrantedAuthority ),GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,然后在做出授权决策时由 AuthorizationManager实例读取。

AuthenticationManager 的常用实现为 ProviderManager,ProviderManager.authenticate 在进行身份验证完成后,会填充 Authentication 对象,其中就包括对象的授权信息(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;
        }
    }

​​​AuthenticationProvider 的实现有很多,这里使用常用的 AbstractUserDetailsAuthenticationProvider 来看一下用户权限的填充步骤。

    //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;
    }

用户权限填充完了,什么时候取用呢?权限信息由授权过滤器 AuthorizationFilter在进行授权的时候取用。

//授权过滤器
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、AuthorizationManager 授权管理器

AuthorizationManager,用来确定 Authentication 是否有权访问特定对象。

在 Spring Security 中 AuthorizationManager 取代了 AccessDecisionManagerAccessDecisionVoter

Spring 官方鼓励自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序改为使用 AuthorizationManager

AuthorizationManager 由 AuthorizationFilter 调用,负责做出最终的访问控制决策。AuthorizationManager 接口包含两个方法://函数式接口

@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);
}

用户可以通过实现 AuthorizationManager 接口来自定义授权控制,同时,Spring Security 附带了一个委托 AuthorizationManager,它可以与各个 AuthorizationManager 进行协作。

RequestMatcherDelegatingAuthorizationManager将选择最合适的 AuthorizationManager 匹配请求。对于方法的安全控制,可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor进行实现。//注意,这两个拦截器在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: 是一个方法拦截器,它使用 AuthorizationManager 来确定一个 Authentication 是否可以调用给定的 MethodInvocation。//该拦截器在方法调用前进行拦截

    //授权管理器
    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();
    }

对应的授权管理器,Spring Security 提供了指定的授权器 PreAuthorizeAuthorizationManager,在该授权器中执行授权相关操作。

*AuthorizationManagerAfterMethodInterceptor:*也是一个MethodInterceptor,它可以使用 AuthorizationManager 来确定一个 Authentication 是否可以访问一个 MethodInvocation 的结果。//这个拦截器是在方法调用结束后进行拦截

授权管理器(AuthorizationManager)的实现类图示:

3、角色的层次化(Roles)

应用程序中的尝尝指定一个特定的角色会自动"包含"其他的角色。例如,在具有"Admin"和"User"角色概念的应用程序中,希望管理员("Admin")能够执行普通用户("User")的所有操作。要实现这一需求,就要确保所有管理员角色也被分配了"User"角色。

角色层次结构允许配置一个角色(或权限)时可以包含其他角色。Spring Security 的 RoleVoter 的扩展版本 RoleHierarchyVoter 配置了一个 RoleHierarchy,它从中获得分配给用户的所有"可访问权限"。//角色选择器

典型的配置可能是这样的:

@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);
}

在上边代码中定义了四个层次结构的角色:

ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST

使用 ROLE_ADMIN 角色进行身份验证的用户,会同时拥有其他角色的权限,其中 ">" 可以被看成"包括"的意思。

注意: RoleHierarchy bean 配置还没有移植到 @EnableMethodSecurity。因此,本例使用的是 AccessDecisionVoter。如果需要 RoleHierarchy 来支持方法的安全性,请继续使用@EnableGlobalMethodSecurity,直到这个bug修复完成。//这个配置是有bug的,后续应该可以正常使用

顺便提一下,Spring Security 也提供了 AccessDecisionManager 和 AccessDecisionVoters 适配 AuthorizationManager 的方式,用来兼容之前的代码,请点击这里

至此,Spring Security 的授权架构介绍完毕,在这篇文章中,更多的是方法论,具体的使用细节将在后续文章中进行补充。

相关推荐
那你为何对我三笑留情5 天前
二、Spring Boot集成Spring Security之实现原理
java·spring boot·spring·spring security
那你为何对我三笑留情7 天前
一、Spring Boot集成Spring Security之自动装配
java·spring boot·spring·spring security
攸攸太上11 天前
Spring Security学习
java·学习·spring·spring security
G皮T2 个月前
【Spring Boot】用 Spring Security 实现后台登录及权限认证功能
spring boot·安全·spring·spring security·认证·登录·授权
左直拳3 个月前
Spring Boot项目的控制器貌似只能get不能post问题
spring boot·spring security·csrf·post不行
代码匠心3 个月前
从零开始学Spring Boot系列-集成Spring Security实现用户认证与授权
java·后端·springboot·spring security
langzitianya3 个月前
Spring Security6 设置免登录接口地址
java·后端·spring·spring security
兴趣广泛的程序猿4 个月前
关于Spring Security的CORS
spring·spring security·cors
Jack_hrx4 个月前
详解 Spring Security:全面保护 Java 应用程序的安全框架
java·安全·spring·spring cloud·spring security
Maiko Star4 个月前
(新)Spring Security如何实现登录认证(实战篇)
服务器·spring security