授权架构
本节介绍适用于授权的 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
对象。
您可以使用 GrantedAuthorityDefaults
来自定义此行为。GrantedAuthorityDefaults
用于允许自定义基于角色的授权规则所使用的前缀。
您可以通过暴露一个 GrantedAuthorityDefaults
bean 来配置授权规则使用不同的前缀,如下所示
-
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>
您可以使用 |
调用处理
Spring Security 提供了拦截器来控制对安全对象的访问,例如方法调用或 Web 请求。关于是否允许调用继续进行的预调用决定由 AuthorizationManager
实例做出。此外,关于给定值是否可以返回的后调用决定也由 AuthorizationManager
实例做出。
AuthorizationManager
AuthorizationManager
取代了 AccessDecisionManager
和 AccessDecisionVoter
。
建议自定义 `AccessDecisionManager` 或 `AccessDecisionVoter` 的应用程序改为使用 AuthorizationManager
。
AuthorizationManager
s 由 Spring Security 的 基于请求的、基于方法的 和 基于消息的 授权组件调用,并负责做出最终的访问控制决定。`AuthorizationManager` 接口包含两个方法
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default void verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager
的 check
方法被传入所有需要做出授权决定的相关信息。特别是,传入安全 Object
使得能够检查实际安全对象调用中包含的参数。例如,假设安全对象是 MethodInvocation
。很容易查询 `MethodInvocation` 以获取任何 Customer
参数,然后在 `AuthorizationManager` 中实现某种安全逻辑以确保主体被允许操作该客户。实现应在授予访问权限时返回肯定的 `AuthorizationDecision`,在拒绝访问权限时返回否定的 `AuthorizationDecision`,并在弃权做出决定时返回 null 的 `AuthorizationDecision`。
verify
调用 check
,并在 AuthorizationDecision
为否定时抛出 AccessDeniedException
。
基于委托的 AuthorizationManager 实现
虽然用户可以实现自己的 AuthorizationManager
来控制授权的所有方面,但 Spring Security 附带了一个委托 `AuthorizationManager`,它可以与单个 `AuthorizationManager` 协作。
RequestMatcherDelegatingAuthorizationManager
会将请求与最合适的委托 `AuthorizationManager` 进行匹配。对于方法安全,您可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
。
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 发布了 AccessDecisionManager
和 AccessDecisionVoter
。
在某些情况下,例如迁移旧应用程序时,可能需要引入一个调用 AccessDecisionManager
或 AccessDecisionVoter
的 AuthorizationManager
。
要调用现有的 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
,您可以执行
-
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);
AccessDecisionManager
的 decide
方法被传入所有需要做出授权决定的相关信息。特别是,传入安全 Object
使得能够检查实际安全对象调用中包含的参数。例如,假设安全对象是 `MethodInvocation`。您可以查询 `MethodInvocation` 以获取任何 Customer
参数,然后在 `AccessDecisionManager` 中实现某种安全逻辑以确保主体被允许操作该客户。在拒绝访问时,实现应抛出 `AccessDeniedException`。
supports(ConfigAttribute)
方法由 `AbstractSecurityInterceptor` 在启动时调用,以确定 `AccessDecisionManager` 是否可以处理传入的 `ConfigAttribute`。`supports(Class)` 方法由安全拦截器实现调用,以确保配置的 `AccessDecisionManager` 支持安全拦截器呈现的安全对象类型。
基于投票的 AccessDecisionManager 实现
虽然用户可以实现自己的 AccessDecisionManager
来控制授权的所有方面,但 Spring Security 包含几个基于投票的 `AccessDecisionManager` 实现。投票决策管理器 描述了相关的类。
下图显示了 AccessDecisionManager
接口

通过使用这种方法,会对一系列 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 网站上,您可以找到一篇博客文章,描述了如何使用投票者实时拒绝已被暂停账户的用户的访问。

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