Authorization Architecture
本节介绍适用于授权的 Spring Security 架构。
Authorities
Authentication
讨论了所有 `Authentication`实现如何存储一个 `GrantedAuthority`对象列表。这些对象表示已授予主体的权限。`GrantedAuthority`对象由 `AuthenticationManager`插入 `Authentication`对象,稍后由 `AccessDecisionManager`实例在做出授权决策时读取。
GrantedAuthority
接口只有一个方法:
String getAuthority();
此方法由 AuthorizationManager
实例用于获取 GrantedAuthority
的精确 String
表示。通过将表示返回为 String
,大多数 AuthorizationManager
实现都可以轻松“读取”GrantedAuthority
。如果 GrantedAuthority
无法精确表示为 String
,则 GrantedAuthority
被视为“复杂”的,且 getAuthority()
必须返回 null
。
复杂 GrantedAuthority
的示例是存储适用于不同客户帐号号的操作和权限阈值的列表的实现。以 String
表示此复杂的 GrantedAuthority
非常困难。因此,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>
您可以使用 |
Invocation Handling
Spring Security 提供控制访问安全对象的拦截器,例如方法调用或 Web 请求。是否允许调用继续的预调用决定由 AuthorizationManager
实例执行。对于给定值是否可以返回的调用后决定也由 AuthorizationManager
实例执行。
The AuthorizationManager
AuthorizationManager
取代了 <<`AccessDecisionManager` 和 AccessDecisionVoter
,authz-legacy-note>>。
鼓励自定义 AccessDecisionManager
或 AccessDecisionVoter
的应用程序 change to using AuthorizationManager
。
AuthorizationManager`s are called by Spring Security’s request-based, method-based, and message-based authorization components and are responsible for making final access control decisions.
The `AuthorizationManager
接口包含两个方法:
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
AuthorizationManager’s `check
方法传递了做出授权决策所需的所有相关信息。特别是,传递安全的 Object
可以检查包含在实际安全对象调用中的那些参数。例如,假设安全对象是 MethodInvocation
。那么很容易为任何 Customer
参数查询 MethodInvocation
,然后在 AuthorizationManager
中实现某种安全逻辑以确保主体被允许对该客户进行操作。预期实现如果授予访问权限,则返回正 AuthorizationDecision
;如果拒绝访问权限,则返回负 AuthorizationDecision
;如果弃权做出决定,则返回 null AuthorizationDecision
。
verify
调用 check
,然后在负 AuthorizationDecision
的情况下引发 AccessDeniedException
。
Delegate-based AuthorizationManager Implementations
虽然用户可以实现自己的 AuthorizationManager
来控制授权的所有方面,但 Spring Security 附带了一个可以与各个 AuthorizationManager
协作的委托 AuthorizationManager
。
RequestMatcherDelegatingAuthorizationManager
将把请求与最合适的委托 AuthorizationManager
匹配。对于方法安全性,可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
。
使用此方法,可以在授权决定中轮询 AuthorizationManager
实现的组成。
AuthorityAuthorizationManager
Spring Security 提供的最常见的 AuthorizationManager
是 AuthorityAuthorizationManager
。它使用给定的权限集在当前 Authentication
上查找权限。如果 Authentication
包含任何配置的权限,则它将返回正 AuthorizationDecision
。否则,它将返回负 AuthorizationDecision
。
AuthenticatedAuthorizationManager
另一个管理程序是 AuthenticatedAuthorizationManager
。它可用于区分匿名用户、完全认证用户和记忆中认证用户。许多网站在记忆中认证下允许某些有限的访问权限,但要求用户通过登录来确认其身份以获得完全访问权限。
AuthorizationManagers
在 {security-api-url}org/springframework/security/authorization/AuthorizationManagers.html[AuthorizationManagers
] 中还有一些有用的静态工厂,用于将各个 AuthorizationManager
组合成更复杂的表达式。
Custom Authorization Managers
显然,您还可以实现一个自定义的 AuthorizationManager
,并且可以在其中放置您想要的任何访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),或者它可以实现一些安全管理逻辑。例如,您可以创建一个可以查询开放策略代理或您自己的授权数据库的实现。
你会在 Spring 网站上发现 blog article,其中描述了如何使用过时的 |
Adapting AccessDecisionManager and AccessDecisionVoters
在 AuthorizationManager
之前,Spring Security 发布了 <<`AccessDecisionManager` 和 AccessDecisionVoter
,authz-legacy-note>>。
在某些情况下(如迁移较旧的应用程序),可能希望引入一个 AuthorizationManager
,它调用 AccessDecisionManager
或 AccessDecisionVoter
。
要调用现有的 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
。
Hierarchical Roles
特定应用程序中的特定角色应自动“包含”其他角色是很常见的要求。例如,在具有“管理员”和“用户”角色概念的应用程序中,您可能希望管理员能够执行普通用户可以执行的所有操作。要实现此目的,您可以确保所有管理员用户也被分配了“用户”角色。或者,您可以修改每个需要“用户”角色的访问约束以包含“管理员”角色。如果您的应用程序中有很多不同的角色,可能会变得相当复杂。
角色层次结构的使用允许您配置哪些角色(或权限)应包含其他角色。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>
|
此示例中,层次结构中有四个角色 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST
。使用 ROLE_ADMIN
认证过的用户在针对适应于调用上述 RoleHierarchyVoter
的 AuthorizationManager
评估安全约束时将表现为拥有所有四个角色。>
符号意为“包含”。
角色层次结构提供了一种简化应用程序的访问控制配置数据和/或减少需要分配给用户权限数目的便捷手段。对于更复杂的要求,您可能希望在您的应用程序要求的特定访问权限和分配给用户的角色之间定义逻辑映射,并在加载用户信息时在两者之间进行转换。
Legacy Authorization Components
Spring Security包含一些旧版组件。由于其尚未被移除,因此包含文档以供参考。建议的替代方法如上。 |
The 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
支持安全拦截器呈现的安全对象类型。
Voting-Based AccessDecisionManager Implementations
虽然用户可以实现自己的 AccessDecisionManager
来控制授权的所有方面,但 Spring Security 包含几个基于投票的 AccessDecisionManager
实现。Voting Decision Manager 描述了相关类。
下图显示了 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
处理。有关详细信息,请参见 {security-api-url}org/springframework/security/access/vote/AuthenticatedVoter.html[AuthenticatedVoter
].
Custom Voters
您还可以实现自定义 AccessDecisionVoter
并放入所需的任何访问控制逻辑。它可能是特定于应用程序(与业务逻辑相关),也可能是实现某些安全管理逻辑。例如,在 Spring 网站上,您可以找到一个 blog article ,描述了如何使用选民实时拒绝帐户已被暂停的用户的访问。
像 Spring Security 的许多其他部分一样,AfterInvocationManager
具有一个单一的具体实现 AfterInvocationProviderManager
,它轮询一个 AfterInvocationProvider`s.
Each `AfterInvocationProvider
列表。该列表允许修改返回对象或抛出一个 AccessDeniedException
。事实上,多个提供程序可以修改对象,因为前一个提供程序的结果会传递给列表中的下一个提供程序。
请注意,如果你正在使用 AfterInvocationManager
,你仍需要允许 MethodSecurityInterceptor’s `AccessDecisionManager
执行操作的配置属性。如果你正在使用典型的 Spring Security 包含 AccessDecisionManager
实现,为特定的安全方法调用没有定义任何配置属性将导致每个 AccessDecisionVoter
弃权。同时,如果 AccessDecisionManager
属性 “allowIfAllAbstainDecisions” 是 false
,将会抛出一个 AccessDeniedException
。你可以通过 (i) 将 “allowIfAllAbstainDecisions” 设置为 true
(尽管通常不建议这样做)或 (ii) 仅确保至少有一个 AccessDecisionVoter
将投票以授予访问权限的配置属性来避免此潜在问题。后者的(推荐)方法通常通过 ROLE_USER
或 ROLE_AUTHENTICATED
配置属性来实现。