授权架构
本节介绍适用于授权的 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
。
Spring Security 的 基于请求的、基于方法的 和 基于消息的 授权组件调用 AuthorizationManager
,并负责做出最终的访问控制决策。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
。它可以用来区分匿名用户、完全认证的用户和记住我认证的用户。许多网站允许在记住我认证下进行某些有限的访问,但要求用户通过登录来确认其身份以获得完全访问权限。
AuthorizationManagers
AuthorizationManagers
中还有一些有用的静态工厂,用于将各个 AuthorizationManager
组合成更复杂的表达式。
自定义 Authorization Managers
显然,您还可以实现自定义 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
中。
角色层次结构
一个常见的要求是应用程序中的特定角色应该自动“包含”其他角色。例如,在一个具有“管理员”和“用户”角色概念的应用程序中,您可能希望管理员能够执行普通用户可以执行的所有操作。为此,您可以确保所有管理员用户也都被分配了“用户”角色。或者,您可以修改每个需要“用户”角色的访问约束,以使其也包含“管理员”角色。如果您的应用程序中有很多不同的角色,这可能会变得非常复杂。
使用角色层次结构允许您配置哪些角色(或权限)应该包含其他角色。这在基于过滤器的授权中(在 HttpSecurity#authorizeHttpRequests
中)和基于方法的授权中(通过 DefaultMethodSecurityExpressionHandler
用于前置/后置注解,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
,可能的值反映在名为 ACCESS_ABSTAIN
、ACCESS_DENIED
和 ACCESS_GRANTED
的 AccessDecisionVoter
静态字段中。如果投票实现对授权决策没有意见,则返回 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
返回一个与一个或多个以 ROLE_
前缀开头的 ConfigAttributes
完全相同的 String
表示形式(来自 getAuthority()
方法),则它会投票授予访问权限。如果没有与任何以 ROLE_
开头的 ConfigAttribute
完全匹配,则 RoleVoter
会投票拒绝访问。如果没有 ConfigAttribute
以 ROLE_
开头,则投票者弃权。
AuthenticatedVoter
我们已经隐式地看到了另一个投票者 AuthenticatedVoter
,它可以用来区分匿名用户、完全认证的用户和记住我认证的用户。许多网站允许在记住我认证下进行某些有限的访问,但要求用户通过登录来确认其身份以获得完全访问权限。
当我们使用 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
配置属性来实现。