授权架构
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
本节介绍了适用于授权的Spring Security架构。
权限
Authentication
讨论了所有 Authentication
实现如何存储 GrantedAuthority
对象的列表。这些对象代表已经授予委托人(principal)的权限。GrantedAuthority
对象由 AuthenticationManager
插入到 Authentication
对象中,随后由 AccessDecisionManager
实例在做出授权决定时读取。
GrantedAuthority
接口只有一个方法。
String getAuthority();
这个方法被 AuthorizationManager
实例用来获取 GrantedAuthority
的一个精确的 String
表示。通过返回一个 String
表示,一个 GrantedAuthority
可以被大多数 AuthorizationManager
实现轻松 "读取"。如果 GrantedAuthority
不能被精确地表示为一个 String
,那么该 GrantedAuthority
被认为是 "复杂的",getAuthority()
必须返回 null
。
一个复杂的 GrantedAuthority
的例子是一个实现,它存储了一个适用于不同客户账号的操作和权限阈值的列表。将这种复杂的 GrantedAuthority
表示为一个 String
将是相当困难的。因此,getAuthority()
方法应该返回 null
。这向任何 AuthorizationManager
表明,它需要支持特定的 GrantedAuthority
实现来理解其内容。
Spring Security 包括一个具体的 GrantedAuthority
实现。SimpleGrantedAuthority
。这个实现允许任何用户指定的字符串被转换为 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
的检查方法被传递给它所需要的所有相关信息,以便做出授权决定。特别是,传递安全对象(secure Object
)使那些包含在实际安全对象调用中的参数能够被检查到。例如,让我们假设安全对象是一个 MethodInvocation
。查询 MethodInvocation
的任何客户参数是很容易的,然后在 AuthorizationManager
中实现某种安全逻辑以确保委托人(principal)被允许对该客户进行操作。如果访问被授予,实现应返回 "negative" 的 AuthorizationDecision
,如果访问被拒绝,应返回 "negative" 的 AuthorizationDecision
,如果不作出决定,则返回空的 AuthorizationDecision
。
verify
调用 check
,然后在出现 "negative" 的 AuthorizationDecision
决定时抛出一个 AccessDeniedException
。
基于授权的AuthorizationManager实现
虽然用户可以实现他们自己的 AuthorizationManager
来控制授权的所有方面(aspect),但Spring Security提供了一个委托的 AuthorizationManager
,可以与个别的 AuthorizationManager
协作。
RequestMatcherDelegatingAuthorizationManager
将把请求与最合适的委托(delegate) AuthorizationManager
相匹配。
对于方法安全,你可以使用 AuthorizationManagerBeforeMethodInterceptor
和 AuthorizationManagerAfterMethodInterceptor
。
使用这种方法,AuthorizationManager
实现的组合可以在授权决定上被轮询。
AuthorityAuthorizationManager
Spring Security提供的最常见的 AuthorizationManager
是 AuthorityAuthorizationManager
。它被配置为在当前 Authentication
中寻找一组给定的授权。如果 Authentication
包含任何配置的授权,它将返回 positive 的 AuthorizationDecision
。否则,它将返回一个 negative 的 AuthorizationDecision
AuthenticatedAuthorizationManager
另一个管理器是 AuthenticatedAuthorizationManager
。它可以用来区分匿名、完全认证和记住我认证的用户。许多网站在Remember-me认证下允许某些有限的访问,但要求用户通过登录来确认他们的身份以获得完整的访问。
AuthenticationManagers
在 AuthenticationManagers
中还有一些有用的静态工厂,用于将单个 AuthenticationManagers
组合成更复杂的表达式。
自定义授权管理器(Authorization Manager)
很明显,你也可以实现一个自定义的 AuthorizationManager
,你可以把你想要的任何访问控制逻辑放在里面。它可能是针对你的应用程序的(与业务逻辑有关),也可能实现一些安全管理逻辑。例如,你可以创建一个可以查询Open Policy Agent或你自己的授权数据库的实现。
你可以在Spring网站上找到一篇 博客,描述了如何使用传统的 AccessDecisionVoter 来实时拒绝账户被暂停的用户的访问。你可以通过实现 AuthorizationManager 来实现同样的结果。
|
调整AccessDecisionManager和AccessDecisionVoter
在 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
,它从其中获得了用户被分配的所有 "可到达的授权(reachable authorities)"。一个典型的配置可能看起来像这样。
-
Java
-
Xml
@Bean
static RoleHierarchy roleHierarchy() {
var hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
}
// 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">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
</value>
</property>
</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
方法被传递给它所需要的所有相关信息,以做出授权决定。特别是,传递安全对象让那些包含在实际安全对象(secure Object
)调用中的参数被检查。例如,假设安全对象是一个 MethodInvocation
。你可以查询 MethodInvocation
的任何 Customer
参数,然后在 AccessDecisionManager
中实现某种安全逻辑,以确保委托人(principal)被允许对该客户进行操作。如果访问被拒绝,实现应抛出一个 AccessDeniedException
。
supports(ConfigAttribute)
方法在启动时由 AbstractSecurityInterceptor
调用,以确定 AccessDecisionManager
是否可以处理传递的 ConfigAttribute
。supports(Class)
方法由安全拦截器实现调用,以确保配置的 AccessDecisionManager
支持安全拦截器提出的安全对象的类型。
基于投票的 AccessDecisionManager 的实现
虽然用户可以实现自己的 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
实现是根据非弃权票的共识授予或拒绝访问。在票数相等或所有票数都弃权的情况下,提供属性来控制行为。如果收到一张或多张 ACCESS_GRANTED
投票,AffirmativeBased
实现就会授予访问权(换句话说,如果至少有一张授予投票,那么拒绝投票将被忽略)。与 ConsensusBased
的实现一样,有一个参数可以控制所有投票者弃权的行为。 UnanimousBased
提供者希望获得一致的 ACCESS_GRANTED
投票以授予访问权,而忽略弃权。如果有任何 ACCESS_DENIED
投票,它就会拒绝访问。与其他实现一样,有一个参数可以控制所有投票者弃权时的行为。
你可以实现一个自定义的 AccessDecisionManager
,以不同的方式计算投票。例如,来自特定 AccessDecisionVoter
的投票可能会得到额外的权重,而来自特定投票者的拒绝投票可能会有否决的效果。
RoleVoter
Spring Security提供的最常用的 AccessDecisionVoter
是 RoleVoter
,它将配置属性视为角色名称,如果用户被分配了该角色,则投票授予访问权。
如果有任何 ConfigAttribute
以 ROLE_
前缀开始,它就会投票。如果有一个 GrantedAuthority
返回的 String
表示(来自 getAuthority()
方法)与一个或多个以 ROLE_
开头的 ConfigAttribute
完全相等,它就投票同意访问。如果没有与任何以 ROLE_
开头的 ConfigAttribute
完全匹配, RoleVoter
投票拒绝访问。如果没有以 ROLE_
开头的 ConfigAttribute
,投票者就会弃权。
AuthenticatedVoter
另一个我们已经隐约看到的投票者是 AuthenticatedVoter
,它可以用来区分匿名、完全认证和remember-me认证的用户。许多网站在Remember-me认证下允许某些有限的访问,但要求用户通过登录来确认他们的身份,以便完全访问。
当我们使用 IS_AUTHENTICATED_ANONYMOUSLY
属性来授予匿名访问权时,这个属性正在被 AuthenticatedVoter
处理。欲了解更多信息,请参见 AuthenticatedVoter
.。
自定义投票人(Voter)
你也可以实现一个自定义的 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
配置属性实现的。