授权架构

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

本节介绍了适用于授权的Spring Security架构。

权限

Authentication 讨论了所有 Authentication 实现如何存储 GrantedAuthority 对象的列表。这些对象代表已经授予委托人(principal)的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,随后由 AccessDecisionManager 实例在做出授权决定时读取。

GrantedAuthority 接口只有一个方法。

String getAuthority();

这个方法被 AuthorizationManager 实例用来获取 GrantedAuthority 的一个精确的 String 表示。通过返回一个 String 表示,一个 GrantedAuthority 可以被大多数 AuthorizationManager 实现轻松 "读取"。如果 GrantedAuthority 不能被精确地表示为一个 String,那么该 GrantedAuthority 被认为是 "复杂的",getAuthority() 必须返回 null

一个复杂的 GrantedAuthority 的例子是一个实现,它存储了一个适用于不同客户账号的操作和权限阈值的列表。将这种复杂的 GrantedAuthority 表示为一个 String 将是相当困难的。因此,getAuthority() 方法应该返回 null。这向任何 AuthorizationManager 表明,它需要支持特定的 GrantedAuthority 实现来理解其内容。

Spring Security 包括一个具体的 GrantedAuthority 实现。SimpleGrantedAuthority。这个实现允许任何用户指定的字符串被转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

默认情况下,基于角色的授权规则包括 ROLE_ 作为前缀。这意味着,如果有一个授权规则要求 security context 的角色是 "USER",Spring Security 将默认寻找返回 "ROLE_USER" 的 GrantedAuthority#getAuthority

你可以用 GrantedAuthorityDefaults 来定制这个。GrantedAuthorityDefaults 的存在是为了允许自定义基于角色的授权规则所使用的前缀。

你可以通过暴露一个 GrantedAuthorityDefaults Bean 来配置授权规则以使用不同的前缀,像这样:

Custom MethodSecurityExpressionHandler
  • Java

  • Kotlin

  • Xml

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}
companion object {
	@Bean
	fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
		return GrantedAuthorityDefaults("MYPREFIX_");
	}
}
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
	<constructor-arg value="MYPREFIX_"/>
</bean>

你使用静态方法暴露 GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的 method security @Configuration 类之前发布它。

调用处理

Spring Security 提供的拦截器可以控制对安全对象的访问,例如方法调用或Web请求。关于调用是否被允许进行的预调用决定是由 AuthorizationManager 实例做出的。同样,关于是否可以返回给定值的调用后决策也是由 AuthorizationManager 实例做出的。

AuthorizationManager

AuthorizationManager 同时取代了 AccessDecisionManagerAccessDecisionVoter

我们鼓励定制 AccessDecisionManagerAccessDecisionVoter 的应用程序 改为使用 AuthorizationManager

AuthorizationManager 被 Spring Security 的 基于请求基于方法基于消息 的授权组件所调用,并负责做出最终的访问控制决定。AuthorizationManager 接口包含两个方法:

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

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

AuthorizationManager 的检查方法被传递给它所需要的所有相关信息,以便做出授权决定。特别是,传递安全对象(secure Object)使那些包含在实际安全对象调用中的参数能够被检查到。例如,让我们假设安全对象是一个 MethodInvocation。查询 MethodInvocation 的任何客户参数是很容易的,然后在 AuthorizationManager 中实现某种安全逻辑以确保委托人(principal)被允许对该客户进行操作。如果访问被授予,实现应返回 "negative" 的 AuthorizationDecision,如果访问被拒绝,应返回 "negative" 的 AuthorizationDecision,如果不作出决定,则返回空的 AuthorizationDecision

verify 调用 check,然后在出现 "negative" 的 AuthorizationDecision 决定时抛出一个 AccessDeniedException

基于授权的AuthorizationManager实现

虽然用户可以实现他们自己的 AuthorizationManager 来控制授权的所有方面(aspect),但Spring Security提供了一个委托的 AuthorizationManager,可以与个别的 AuthorizationManager 协作。

RequestMatcherDelegatingAuthorizationManager 将把请求与最合适的委托(delegate) AuthorizationManager 相匹配。 对于方法安全,你可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager Implementations 说明了相关的类。

authorizationhierarchy
Figure 1. Authorization Manager Implementations

使用这种方法,AuthorizationManager 实现的组合可以在授权决定上被轮询。

AuthorityAuthorizationManager

Spring Security提供的最常见的 AuthorizationManagerAuthorityAuthorizationManager。它被配置为在当前 Authentication 中寻找一组给定的授权。如果 Authentication 包含任何配置的授权,它将返回 positive 的 AuthorizationDecision。否则,它将返回一个 negative 的 AuthorizationDecision

AuthenticatedAuthorizationManager

另一个管理器是 AuthenticatedAuthorizationManager。它可以用来区分匿名、完全认证和记住我认证的用户。许多网站在Remember-me认证下允许某些有限的访问,但要求用户通过登录来确认他们的身份以获得完整的访问。

AuthenticationManagers

AuthenticationManagers 中还有一些有用的静态工厂,用于将单个 AuthenticationManagers 组合成更复杂的表达式。

自定义授权管理器(Authorization Manager)

很明显,你也可以实现一个自定义的 AuthorizationManager,你可以把你想要的任何访问控制逻辑放在里面。它可能是针对你的应用程序的(与业务逻辑有关),也可能实现一些安全管理逻辑。例如,你可以创建一个可以查询Open Policy Agent或你自己的授权数据库的实现。

你可以在Spring网站上找到一篇 博客,描述了如何使用传统的 AccessDecisionVoter 来实时拒绝账户被暂停的用户的访问。你可以通过实现 AuthorizationManager 来实现同样的结果。

调整AccessDecisionManager和AccessDecisionVoter

AuthorizationManager 之前,Spring Security发布了 AccessDecisionManagerAccessDecisionVoter

在某些情况下,比如迁移一个旧的应用程序,可能需要引入一个调用 AccessDecisionManagerAccessDecisionVoterAuthorizationManager

要调用一个现有的 AccessDecisionManager,你可以这样做。

Adapting an AccessDecisionManager
  • Java

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

然后将其注入到你的 SecurityFilterChain

或者只调用一个 AccessDecisionVoter,你可以这样做。

Adapting an AccessDecisionVoter
  • Java

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

然后将其接入你的 SecurityFilterChain

层次分明的角色

在一个应用程序中,一个特定的角色应该自动 "包括" 其他角色,这是一个常见的要求。例如,在一个有 "管理员" 和 "用户" 角色概念的应用程序中,你可能希望管理员能够做所有普通用户能做的事情。为了达到这个目的,你可以确保所有的管理员用户也被分配为 "用户" 角色。或者,你可以修改每个需要 "用户" 角色的访问限制,使其也包括 "管理员" 角色。如果你的应用程序中有很多不同的角色,这可能会变得相当复杂。

使用角色层次结构允许你配置哪些角色(或权限)应该包括其他人。Spring Security的 RoleVoter 的一个扩展版本,RoleHierarchyVoter,被配置了一个 RoleHierarchy,它从其中获得了用户被分配的所有 "可到达的授权(reachable authorities)"。一个典型的配置可能看起来像这样。

Hierarchical Roles Configuration
  • Java

  • Xml

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

// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setRoleHierarchy(roleHierarchy);
	return expressionHandler;
}
<bean id="roleHierarchy"
		class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
	<property name="hierarchy">
		<value>
			ROLE_ADMIN > ROLE_STAFF
			ROLE_STAFF > ROLE_USER
			ROLE_USER > ROLE_GUEST
		</value>
	</property>
</bean>

<!-- and, if using method security also add -->
<bean id="methodSecurityExpressionHandler"
        class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
    <property ref="roleHierarchy"/>
</bean>
RoleHierarchy Bean 的配置还没有被移植到 @EnableMethodSecurity。因此,这个例子使用的是 AccessDecisionVoter。如果你需要 RoleHierarchy 对方法安全的支持,请继续使用 @EnableGlobalMethodSecurity 直到 github.com/spring-projects/spring-security/issues/12783 完成。

在这里,我们在一个层次结构中有四个角色 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST。一个被认证为 ROLE_ADMIN 的用户,当安全约束被评估为适应调用上述 RoleHierarchyVoterAuthorizationManager 时,将表现得好像他们拥有所有四个角色。> 符号可以被认为是 "包括" 的意思。

角色分层提供了一种方便的方法,可以简化你的应用程序的访问控制配置数据和/或减少你需要分配给用户的权限数量。对于更复杂的要求,你可能希望在你的应用程序需要的特定访问权限和分配给用户的角色之间定义一个逻辑映射,在加载用户信息时在两者之间进行转换。

遗留的授权组件

Spring Security包含一些遗留的组件。由于它们还没有被删除,所以为了历史的目的,包括了文档。它们的推荐替代物在上面。

AccessDecisionManager

AccessDecisionManagerAbstractSecurityInterceptor 调用,负责做出最终的访问控制决定。 AccessDecisionManager 接口包含三个方法。

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManagerdecide 方法被传递给它所需要的所有相关信息,以做出授权决定。特别是,传递安全对象让那些包含在实际安全对象(secure Object)调用中的参数被检查。例如,假设安全对象是一个 MethodInvocation。你可以查询 MethodInvocation 的任何 Customer 参数,然后在 AccessDecisionManager 中实现某种安全逻辑,以确保委托人(principal)被允许对该客户进行操作。如果访问被拒绝,实现应抛出一个 AccessDeniedException

supports(ConfigAttribute) 方法在启动时由 AbstractSecurityInterceptor 调用,以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttributesupports(Class) 方法由安全拦截器实现调用,以确保配置的 AccessDecisionManager 支持安全拦截器提出的安全对象的类型。

基于投票的 AccessDecisionManager 的实现

虽然用户可以实现自己的 AccessDecisionManager 来控制授权的所有方面,但Spring Security包括几个基于投票的 AccessDecisionManager 的实现。Voting Decision Manager 描述了相关的类。

下面的图片显示了 AccessDecisionManager 的接口。

access decision voting
Figure 2. Voting Decision Manager

通过使用这种方法,一系列的 AccessDecisionVoter 实现会对授权决定进行投票。然后, AccessDecisionManager 根据其对投票的评估决定是否抛出一个 AccessDeniedException

AccessDecisionVoter 接口有三个方法。

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体的实现会返回一个 int,可能的值反映在名为 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTEDAccessDecisionVoter 静态字段中。如果投票实现对授权决策没有意见,则返回 ACCESS_ABSTAIN。如果它有意见,则必须返回 ACCESS_DENIEDACCESS_GRANTED。

Spring Security 提供了三种具体的 AccessDecisionManager 实现来统计投票。ConsensusBased 实现是根据非弃权票的共识授予或拒绝访问。在票数相等或所有票数都弃权的情况下,提供属性来控制行为。如果收到一张或多张 ACCESS_GRANTED 投票,AffirmativeBased 实现就会授予访问权(换句话说,如果至少有一张授予投票,那么拒绝投票将被忽略)。与 ConsensusBased 的实现一样,有一个参数可以控制所有投票者弃权的行为。 UnanimousBased 提供者希望获得一致的 ACCESS_GRANTED 投票以授予访问权,而忽略弃权。如果有任何 ACCESS_DENIED 投票,它就会拒绝访问。与其他实现一样,有一个参数可以控制所有投票者弃权时的行为。

你可以实现一个自定义的 AccessDecisionManager,以不同的方式计算投票。例如,来自特定 AccessDecisionVoter 的投票可能会得到额外的权重,而来自特定投票者的拒绝投票可能会有否决的效果。

RoleVoter

Spring Security提供的最常用的 AccessDecisionVoterRoleVoter,它将配置属性视为角色名称,如果用户被分配了该角色,则投票授予访问权。

如果有任何 ConfigAttributeROLE_ 前缀开始,它就会投票。如果有一个 GrantedAuthority 返回的 String 表示(来自 getAuthority() 方法)与一个或多个以 ROLE_ 开头的 ConfigAttribute 完全相等,它就投票同意访问。如果没有与任何以 ROLE_ 开头的 ConfigAttribute 完全匹配, RoleVoter 投票拒绝访问。如果没有以 ROLE_ 开头的 ConfigAttribute,投票者就会弃权。

AuthenticatedVoter

另一个我们已经隐约看到的投票者是 AuthenticatedVoter,它可以用来区分匿名、完全认证和remember-me认证的用户。许多网站在Remember-me认证下允许某些有限的访问,但要求用户通过登录来确认他们的身份,以便完全访问。

当我们使用 IS_AUTHENTICATED_ANONYMOUSLY 属性来授予匿名访问权时,这个属性正在被 AuthenticatedVoter 处理。欲了解更多信息,请参见 AuthenticatedVoter.。

自定义投票人(Voter)

你也可以实现一个自定义的 AccessDecisionVoter,把你想要的任何访问控制逻辑都放在里面。它可能是针对你的应用程序的(与业务逻辑有关),也可能实现一些安全管理逻辑。例如,在Spring网站上,你可以找到一篇 博客,描述了如何使用一个投票器来实时拒绝账户被暂停的用户的访问。

after invocation
Figure 3. After Invocation Implementation

像 Spring Security 的许多其他部分一样,AfterInvocationManager 有一个单一的具体实现,即 AfterInvocationProviderManager,它轮询 AfterInvocationProvider 列表。每个 AfterInvocationProvider 都被允许修改返回对象或抛出一个 AccessDeniedException。事实上,多个提供者可以修改该对象,因为前一个提供者的结果会传递给列表中的下一个。

请注意,如果你使用 AfterInvocationManager,你仍然需要允许 MethodSecurityInterceptorAccessDecisionManager 允许操作的配置属性。如果你使用典型的Spring Security包含的 AccessDecisionManager 实现,没有为某个安全方法调用定义配置属性将导致每个 AccessDecisionVoter 投弃权票。反过来,如果 AccessDecisionManager 属性 “allowIfAllAbstainDecisions” 为 false,就会抛出一个 AccessDeniedException。你可以通过以下方式避免这个潜在的问题:(i)将 “allowIfAllAbstainDecisions” 设置为 true(尽管通常不推荐这样做),或者(ii)简单地确保至少有一个配置属性是 AccessDecisionVoter 会投票授予访问权的。后一种(推荐)方法通常是通过 ROLE_USERROLE_AUTHENTICATED 配置属性实现的。