跳到主要内容
版本:7.0.2

授权架构

DeepSeek V3 中英对照 Authorization Architecture

本节描述了适用于授权的Spring Security架构。

权限​

身份验证 讨论了所有 Authentication 实现如何存储一个 GrantedAuthority 对象列表。这些对象代表已授予主体的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,随后在做出授权决策时由 AuthorizationManager 实例读取。

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 默认会查找一个 GrantedAuthority#getAuthority 方法返回 "ROLE_USER" 的授权对象。

您可以使用 GrantedAuthorityDefaults 来自定义此设置。GrantedAuthorityDefaults 的存在是为了允许自定义基于角色的授权规则所使用的前缀。

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

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

你通过 static 方法暴露 GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全 @Configuration 类之前发布它

调用处理​

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

授权管理器​

AuthorizationManager 取代了 AccessDecisionManager 和 AccessDecisionVoter。

自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序建议改用 AuthorizationManager。

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

AuthorizationResult authorize(Supplier<Authentication> authentication, Object secureObject);

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

AuthorizationManager 的 check 方法会接收做出授权决策所需的所有相关信息。特别是,传递安全的 Object 对象使得能够检查实际安全对象调用中包含的参数。例如,假设安全对象是一个 MethodInvocation。我们可以轻松查询 MethodInvocation 中的任何 Customer 参数,然后在 AuthorizationManager 中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果允许访问,实现应返回一个肯定的 AuthorizationDecision;如果拒绝访问,则返回否定的 AuthorizationDecision;当弃权不做出决定时,则返回一个空的 AuthorizationDecision。

verify 调用 authorize,并在 AuthorizationDecision 为否定结果时抛出 AccessDeniedException。

基于委托的 AuthorizationManager 实现​

虽然用户可以自行实现 AuthorizationManager 来控制授权的各个方面,但 Spring Security 自带了一个委托式 AuthorizationManager,它能够与各个独立的 AuthorizationManager 协同工作。

RequestMatcherDelegatingAuthorizationManager 将根据请求匹配最合适的委托 AuthorizationManager。对于方法级安全,你可以使用 AuthorizationManagerBeforeMethodInterceptor 和 AuthorizationManagerAfterMethodInterceptor。

授权管理器实现 展示了相关类。

授权层级

图 1. 授权管理器实现

采用这种方法,可以在授权决策时轮询多个AuthorizationManager实现的组合。

AuthorityAuthorizationManager​

Spring Security 中最常见的 AuthorizationManager 是 AuthorityAuthorizationManager。它被配置为在当前 Authentication 中查找一组给定的权限。如果 Authentication 包含任何已配置的权限,它将返回一个肯定的 AuthorizationDecision。否则,它将返回一个否定的 AuthorizationDecision。

经过身份验证的授权管理器​

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

创建 AuthorizationManager 实例​

AuthorizationManagerFactory 接口(在 Spring Security 7.0 中引入)用于在基于请求的和基于方法的授权组件中创建通用的 AuthorizationManager。以下是 AuthorizationManagerFactory 接口的概览:

public interface AuthorizationManagerFactory<T> {
AuthorizationManager<T> permitAll();
AuthorizationManager<T> denyAll();
AuthorizationManager<T> hasRole(String role);
AuthorizationManager<T> hasAnyRole(String... roles);
AuthorizationManager<T> hasAllRoles(String... roles);
AuthorizationManager<T> hasAuthority(String authority);
AuthorizationManager<T> hasAnyAuthority(String... authorities);
AuthorizationManager<T> hasAllAuthorities(String... authorities);
AuthorizationManager<T> authenticated();
AuthorizationManager<T> fullyAuthenticated();
AuthorizationManager<T> rememberMe();
AuthorizationManager<T> anonymous();
}

默认实现是 DefaultAuthorizationManagerFactory,它允许自定义提供给工厂创建的 AuthorizationManager 的 rolePrefix(默认为 "ROLE_")、RoleHierarchy 和 AuthenticationTrustManager。

为了自定义Spring Security使用的默认实例,只需发布一个bean,如下例所示:

@Bean
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
new DefaultAuthorizationManagerFactory<>();
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
authorizationManagerFactory.setRolePrefix("role_");

return authorizationManagerFactory;
}
提示

您也可以通过提供具体的参数化类型(而非泛型类型)来针对 Spring Security 中此工厂的特定用途进行定制。具体示例请参阅文档中基于请求和基于方法的授权管理器定制章节。

除了简单地自定义 AuthorizationManagerFactory 的默认实例外,您还可以提供自己的实现,以完全自定义工厂创建的实例并提供您自己的实现。

备注

实际接口 为所有工厂方法提供了默认实现,这使得自定义实现只需实现需要定制的方法即可。

授权管理器​

在 AuthorizationManagers 中,还提供了有用的静态工厂方法,用于将多个独立的 AuthorizationManager 组合成更复杂的表达式。

自定义授权管理器​

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

提示

你可以在 Spring 官网上找到一篇博客文章,其中描述了如何使用传统的 AccessDecisionVoter 来实时拒绝已被暂停账户的用户的访问。你也可以通过实现 AuthorizationManager 来达到相同的效果。

适配AccessDecisionManager和AccessDecisionVoters​

在AuthorizationManager之前,Spring Security 发布了 AccessDecisionManager 和 AccessDecisionVoter。

在某些情况下,例如迁移旧版应用程序时,可能需要引入一个调用 AccessDecisionManager 或 AccessDecisionVoter 的 AuthorizationManager。

要调用现有的 AccessDecisionManager,你可以这样做:

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

@Override
public AuthorizationResult authorize(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,可以这样做:

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

@Override
public AuthorizationResult authorize(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。

分层角色​

在应用程序中,一个常见的需求是某个特定角色应自动"包含"其他角色。例如,在一个包含"管理员"和"普通用户"角色概念的应用程序中,你可能希望管理员能够执行普通用户的所有操作。为实现这一目标,你可以确保所有管理员用户同时被分配"普通用户"角色。或者,你也可以修改每个需要"普通用户"角色的访问约束,使其同时包含"管理员"角色。如果应用程序中存在大量不同的角色,这种做法可能会变得相当复杂。

使用角色层次结构(role-hierarchy)可以配置哪些角色(或权限)应包含其他角色。这一功能在基于过滤器的授权中通过 HttpSecurity#authorizeHttpRequests 支持,同时在基于方法的授权中,通过以下方式支持:预后置注解使用 DefaultMethodSecurityExpressionHandler,@Secured 注解使用 SecuredAuthorizationManager,JSR-250 注解使用 Jsr250AuthorizationManager。你可以通过以下方式一次性为所有这些配置其行为:

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

在一个层级结构中,我们有四个角色:ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST。当针对任何基于过滤器或方法的规则进行安全约束评估时,一个通过 ROLE_ADMIN 身份验证的用户,其行为将如同拥有全部四个角色。

提示

符号 > 可以理解为表示“包含”的意思。

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

旧版授权组件​

备注

Spring Security 包含一些遗留组件。由于它们尚未被移除,此处保留相关文档以供历史参考。建议的替代方案已在上方列出。

在访问传统授权组件时,请同时包含 spring-security-access 依赖,如下所示:

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-access</artifactId>
</dependency>

访问决策管理器​

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

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManager 的 decide 方法会接收所有做出授权决策所需的相关信息。特别是传入的安全 Object 允许检查实际安全对象调用中包含的参数。例如,假设安全对象是一个 MethodInvocation。你可以查询 MethodInvocation 中的任何 Customer 参数,然后在 AccessDecisionManager 中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果访问被拒绝,实现应抛出 AccessDeniedException。

supports(ConfigAttribute) 方法在启动时由 AbstractSecurityInterceptor 调用,用于判断 AccessDecisionManager 能否处理传入的 ConfigAttribute。而 supports(Class) 方法则由安全拦截器实现调用,以确保配置的 AccessDecisionManager 支持该安全拦截器所呈现的安全对象类型。

基于投票的访问决策管理器实现​

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

下图展示了 AccessDecisionManager 接口:

访问决策投票

图 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,它可以用来区分匿名用户、完全认证用户和“记住我”认证用户。许多网站在“记住我”认证下允许某些有限的访问,但要求用户通过登录来确认身份以获得完全访问权限。

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

自定义投票器​

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

调用后

图 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 配置属性来实现。