授权架构

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

权限

Authentication 讨论了所有 Authentication 实现如何存储 GrantedAuthority 对象列表。这些对象代表授予主体的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,并在稍后由 AccessDecisionManager 实例在做出授权决定时读取。

GrantedAuthority 接口只有一个方法

String getAuthority();

此方法由 AuthorizationManager 实例用于获取 GrantedAuthority 的精确 String 表示形式。通过返回 String 表示形式,大多数 AuthorizationManager 实现可以轻松“读取” GrantedAuthority。如果 GrantedAuthority 不能精确地表示为 String,则 GrantedAuthority 被认为是“复杂的”,并且 getAuthority() 必须返回 null

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

Spring Security 包含一个具体的 GrantedAuthority 实现:SimpleGrantedAuthority。此实现允许将任何用户指定的 String 转换为 GrantedAuthority。安全架构附带的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

默认情况下,基于角色的授权规则包含 ROLE_ 作为前缀。这意味着如果存在要求安全上下文具有“USER”角色的授权规则,Spring Security 默认会查找返回“ROLE_USER”的 GrantedAuthority#getAuthority

您可以使用 GrantedAuthorityDefaults 来自定义此行为。GrantedAuthorityDefaults 用于允许自定义基于角色的授权规则所使用的前缀。

您可以通过暴露一个 GrantedAuthorityDefaults bean 来配置授权规则使用不同的前缀,如下所示

自定义 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>

您可以使用 static 方法暴露 GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全 `@Configuration` 类之前发布它

调用处理

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

AuthorizationManager

AuthorizationManager 取代了 AccessDecisionManagerAccessDecisionVoter

建议自定义 `AccessDecisionManager` 或 `AccessDecisionVoter` 的应用程序改为使用 AuthorizationManager

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

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

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

AuthorizationManagercheck 方法被传入所有需要做出授权决定的相关信息。特别是,传入安全 Object 使得能够检查实际安全对象调用中包含的参数。例如,假设安全对象是 MethodInvocation。很容易查询 `MethodInvocation` 以获取任何 Customer 参数,然后在 `AuthorizationManager` 中实现某种安全逻辑以确保主体被允许操作该客户。实现应在授予访问权限时返回肯定的 `AuthorizationDecision`,在拒绝访问权限时返回否定的 `AuthorizationDecision`,并在弃权做出决定时返回 null 的 `AuthorizationDecision`。

verify 调用 check,并在 AuthorizationDecision 为否定时抛出 AccessDeniedException

基于委托的 AuthorizationManager 实现

虽然用户可以实现自己的 AuthorizationManager 来控制授权的所有方面,但 Spring Security 附带了一个委托 `AuthorizationManager`,它可以与单个 `AuthorizationManager` 协作。

RequestMatcherDelegatingAuthorizationManager 会将请求与最合适的委托 `AuthorizationManager` 进行匹配。对于方法安全,您可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager 实现 说明了相关的类。

authorizationhierarchy
图 1. Authorization Manager 实现

使用这种方法,可以对 AuthorizationManager 实现的组合进行授权决策的投票。

AuthorityAuthorizationManager

Spring Security 提供的最常见的 `AuthorizationManager` 是 AuthorityAuthorizationManager。它配置了一组给定的权限,以便在当前 `Authentication` 上查找。如果 `Authentication` 包含任何配置的权限,它将返回肯定的 `AuthorizationDecision`。否则,它将返回否定的 `AuthorizationDecision`。

AuthenticatedAuthorizationManager

另一个管理器是 `AuthenticatedAuthorizationManager`。它可以用于区分匿名用户、完全认证用户和 remember-me 认证用户。许多网站在 remember-me 认证下允许某些有限的访问,但要求用户通过登录确认身份才能获得完全访问权限。

AuthorizationManagers

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

自定义 Authorization Manager

显然,您还可以实现自定义 AuthorizationManager,并在其中放入几乎任何您想要的访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),或者它可以实现一些安全管理逻辑。例如,您可以创建一个可以查询 Open Policy Agent 或您自己的授权数据库的实现。

您会在 Spring 网站上找到一篇博客文章,描述了如何使用遗留的 AccessDecisionVoter 实时拒绝已被暂停账户的用户的访问。您可以通过实现 AuthorizationManager 来达到同样的效果。

适配 AccessDecisionManager 和 AccessDecisionVoters

在 `AuthorizationManager` 之前,Spring Security 发布了 AccessDecisionManagerAccessDecisionVoter

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

要调用现有的 AccessDecisionManager,您可以执行

适配 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,您可以执行

适配 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 中。

角色层级

一个常见的需求是,应用程序中的某个特定角色应自动“包含”其他角色。例如,在一个具有“admin”和“user”角色概念的应用程序中,您可能希望 admin 能够执行普通用户可以执行的所有操作。为了实现这一点,您可以确保所有 admin 用户也被分配了“user”角色。或者,您可以修改每个需要“user”角色的访问约束,使其也包含“admin”角色。如果您的应用程序中有很多不同的角色,这会变得相当复杂。

使用角色层级允许您配置哪些角色(或权限)应该包含其他角色。`HttpSecurity#authorizeHttpRequests` 中支持基于过滤器的授权,通过 `DefaultMethodSecurityExpressionHandler` 支持 pre-post 注解、`SecuredAuthorizationManager` 支持 `@Secured` 以及 `Jsr250AuthorizationManager` 支持 JSR-250 注解来实现基于方法的授权。您可以一次性按照以下方式配置它们的行为

角色层级配置
  • Java

  • Xml

@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("STAFF")
        .role("STAFF").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}

// and, if using pre-post 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" factory-method="fromHierarchy">
	<constructor-arg>
		<value>
			ROLE_ADMIN > ROLE_STAFF
			ROLE_STAFF > ROLE_USER
			ROLE_USER > ROLE_GUEST
		</value>
	</constructor-arg>
</bean>

<!-- and, if using method security also add -->
<bean id="methodSecurityExpressionHandler"
        class="org.springframework.security.access.expression.method.MethodSecurityExpressionHandler">
    <property ref="roleHierarchy"/>
</bean>

这里我们有一个包含四个角色的层级:ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST。使用 `ROLE_ADMIN` 进行认证的用户,在根据任何基于过滤器或方法规则评估安全约束时,将表现得好像拥有所有这四个角色一样。

> 符号可以被认为是表示“包含”。

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

遗留授权组件

Spring Security 包含一些遗留组件。由于它们尚未移除,此处保留文档以供历史参考。建议使用上面推荐的替代方案。

AccessDecisionManager

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

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManagerdecide 方法被传入所有需要做出授权决定的相关信息。特别是,传入安全 Object 使得能够检查实际安全对象调用中包含的参数。例如,假设安全对象是 `MethodInvocation`。您可以查询 `MethodInvocation` 以获取任何 Customer 参数,然后在 `AccessDecisionManager` 中实现某种安全逻辑以确保主体被允许操作该客户。在拒绝访问时,实现应抛出 `AccessDeniedException`。

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

基于投票的 AccessDecisionManager 实现

虽然用户可以实现自己的 AccessDecisionManager 来控制授权的所有方面,但 Spring Security 包含几个基于投票的 `AccessDecisionManager` 实现。投票决策管理器 描述了相关的类。

下图显示了 AccessDecisionManager 接口

access decision voting
图 2. 投票决策管理器

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

AccessDecisionVoter 接口有三个方法

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体实现返回一个 `int` 值,其可能值反映在 `AccessDecisionVoter` 的静态字段 `ACCESS_ABSTAIN`、`ACCESS_DENIED` 和 `ACCESS_GRANTED` 中。如果投票实现对授权决定没有意见,则返回 `ACCESS_ABSTAIN`。如果它有意见,则必须返回 `ACCESS_DENIED` 或 `ACCESS_GRANTED`。

Spring Security 提供了三个具体的 `AccessDecisionManager` 实现来统计投票。`ConsensusBased` 实现根据非弃权投票的共识来授予或拒绝访问。提供了属性来控制投票相等或所有投票弃权时的行为。`AffirmativeBased` 实现如果收到一个或多个 `ACCESS_GRANTED` 投票则授予访问权限(换句话说,只要至少有一个授予投票,拒绝投票将被忽略)。与 `ConsensusBased` 实现一样,有一个参数控制所有投票者弃权时的行为。`UnanimousBased` 提供者要求一致的 `ACCESS_GRANTED` 投票才能授予访问权限,忽略弃权。如果存在任何 `ACCESS_DENIED` 投票,则拒绝访问。与其它实现一样,有一个参数控制所有投票者弃权时的行为。

您可以实现一个自定义 `AccessDecisionManager`,以不同的方式统计投票。例如,来自特定 `AccessDecisionVoter` 的投票可能会获得额外的权重,而来自特定投票者的拒绝投票可能会具有否决权。

RoleVoter

Spring Security 提供的最常用的 `AccessDecisionVoter` 是 RoleVoter,它将配置属性视为角色名,并在用户被分配了该角色时投票授予访问权限。

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

AuthenticatedVoter

我们隐式看到的另一个投票者是 `AuthenticatedVoter`,它可以用于区分匿名用户、完全认证用户和 remember-me 认证用户。许多网站在 remember-me 认证下允许某些有限的访问,但要求用户通过登录确认身份才能获得完全访问权限。

当我们使用 `IS_AUTHENTICATED_ANONYMOUSLY` 属性授予匿名访问时,该属性由 `AuthenticatedVoter` 处理。更多信息,请参阅AuthenticatedVoter

自定义投票者

您还可以实现自定义 `AccessDecisionVoter`,并在其中放入几乎任何您想要的访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),或者它可以实现一些安全管理逻辑。例如,在 Spring 网站上,您可以找到一篇博客文章,描述了如何使用投票者实时拒绝已被暂停账户的用户的访问。

after invocation
图 3. 调用后实现

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

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