授权架构
本节介绍适用于授权的 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
由 Spring Security 的基于请求的、基于方法的和基于消息的授权组件调用,并且负责做出最终的访问控制决策。AuthorizationManager
接口包含两种方法
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager
的 check
方法传递了做出授权决策所需的所有相关信息。特别是,传递安全的 Object
可以检查实际安全对象调用中包含的参数。例如,假设安全对象是 MethodInvocation
。可以轻松地查询 MethodInvocation
以获取任何 Customer
参数,然后在 AuthorizationManager
中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果授予访问权限,则实现应返回正 AuthorizationDecision
;如果拒绝访问权限,则实现应返回负 AuthorizationDecision
;如果弃权做出决策,则实现应返回空 AuthorizationDecision
。
verify
调用 check
,并在 AuthorizationDecision
为负时随后抛出 AccessDeniedException
。
基于委托的 AuthorizationManager 实现
虽然用户可以实现自己的 AuthorizationManager
来控制授权的所有方面,但 Spring Security 附带了一个委托 AuthorizationManager
,它可以与各个 AuthorizationManager
协作。
RequestMatcherDelegatingAuthorizationManager
将请求与最合适的委托 AuthorizationManager
匹配。对于方法安全性,可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
。
授权管理器实现说明了相关类。
使用此方法,可以对 AuthorizationManager
实现的组合进行授权决策轮询。
AuthorityAuthorizationManager
Spring Security 提供的最常见的 AuthorizationManager
是 AuthorityAuthorizationManager
。它配置了一组给定的权限,用于在当前 Authentication
上查找。如果 Authentication
包含任何配置的权限,它将返回正 AuthorizationDecision
。否则,它将返回负 AuthorizationDecision
。
AuthenticatedAuthorizationManager
另一个管理器是 AuthenticatedAuthorizationManager
。它可用于区分匿名用户、完全认证用户和记住我认证用户。许多网站允许在记住我认证下进行某些有限访问,但要求用户通过登录来确认其身份以获得完全访问权限。
AuthorizationManagers
在 AuthorizationManagers
中还有一些有用的静态工厂,用于将各个 AuthorizationManager
组成更复杂的表达式。
自定义授权管理器
显然,您还可以实现一个自定义 AuthorizationManager
,并且可以在其中放置所需的任何访问控制逻辑。它可能是特定于您的应用程序(与业务逻辑相关)的,或者它可能实现一些安全管理逻辑。例如,您可以创建一个可以查询开放策略代理或您自己的授权数据库的实现。
您会在 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
。
分层角色
应用程序中的特定角色应自动“包含”其他角色,这是一个常见要求。例如,在一个具有“管理员”和“用户”角色概念的应用程序中,您可能希望管理员能够执行普通用户可以执行的所有操作。为实现此目的,您可以确保所有管理员用户也都被分配了“用户”角色。或者,您可以修改需要“用户”角色的每个访问约束,以同时包含“管理员”角色。如果您在应用程序中有很多不同的角色,这可能会变得非常复杂。
使用角色层次结构允许您配置哪些角色(或权限)应包含其他角色。Spring Security 的 RoleVoter
的扩展版本 RoleHierarchyVoter
使用 RoleHierarchy
进行配置,从中获取分配给用户的全部“可达权限”。典型的配置可能如下所示
-
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 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>
RoleHierarchy bean 配置尚未移植到 @EnableMethodSecurity 。因此,此示例使用 AccessDecisionVoter 。如果您需要方法安全性的 RoleHierarchy 支持,请继续使用 @EnableGlobalMethodSecurity ,直到 github.com/spring-projects/spring-security/issues/12783 完成。
|
这里我们在一个分层结构中具有四个角色 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST
。使用 ROLE_ADMIN
进行身份验证的用户在针对调用上述 RoleHierarchyVoter
而调整的 AuthorizationManager
评估安全约束时,将表现得好像他们具有所有四个角色。>
符号可以理解为“包含”。
角色分层提供了一种简化应用程序访问控制配置数据和/或减少需要分配给用户的权限数量的便捷方法。对于更复杂的要求,您可能希望在应用程序所需的特定访问权限和分配给用户的角色之间定义一个逻辑映射,在加载用户信息时在两者之间进行转换。
旧版授权组件
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
。
AbstractSecurityInterceptor
在启动时会调用 supports(ConfigAttribute)
方法,以确定 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
,其返回一个String
表示(来自getAuthority()
方法),与一个或多个以ROLE_
前缀开头的ConfigAttributes
完全相等,它会投票授予访问权限。如果没有以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
配置属性实现。