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 对象。

默认情况下,基于角色的授权规则包含 ROLE_ 作为前缀。这意味着,如果有一个授权规则要求安全上下文中具有“USER”的角色,则 Spring Security 默认会查找返回“ROLE_USER”的 GrantedAuthority#getAuthority

您可以使用 GrantedAuthorityDefaults 自定义此内容。GrantedAuthorityDefaults 存在允许自定义基于角色的授权规则所使用的前缀。

您可以通过公开一个 GrantedAuthorityDefaults bean 来配置授权规则以使用不同的前缀,如下所示:

Custom MethodSecurityExpressionHandler
  • 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>

您可以使用 static 方法公开 GrantedAuthorityDefaults,以确保 Spring 在初始化 Spring Security 的方法安全 @Configuration 类之前发布它。

Invocation Handling

Spring Security 提供控制访问安全对象的拦截器,例如方法调用或 Web 请求。是否允许调用继续的预调用决定由 AuthorizationManager 实例执行。对于给定值是否可以返回的调用后决定也由 AuthorizationManager 实例执行。

The AuthorizationManager

AuthorizationManager 取代了 <<`AccessDecisionManager` 和 AccessDecisionVoter,authz-legacy-note>>。

鼓励自定义 AccessDecisionManagerAccessDecisionVoter 的应用程序 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 匹配。对于方法安全性,可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager Implementations 说明了相关的类。

authorizationhierarchy
Figure 1. Authorization Manager Implementations

使用此方法,可以在授权决定中轮询 AuthorizationManager 实现的组成。

AuthorityAuthorizationManager

Spring Security 提供的最常见的 AuthorizationManagerAuthorityAuthorizationManager。它使用给定的权限集在当前 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,其中描述了如何使用过时的 AccessDecisionVoter 实时拒绝被暂停的用户的访问。你可以通过实现 AuthorizationManager 来实现相同的结果。

Adapting AccessDecisionManager and AccessDecisionVoters

AuthorizationManager 之前,Spring Security 发布了 <<`AccessDecisionManager` 和 AccessDecisionVoter,authz-legacy-note>>。

在某些情况下(如迁移较旧的应用程序),可能希望引入一个 AuthorizationManager,它调用 AccessDecisionManagerAccessDecisionVoter

要调用现有的 AccessDecisionManager,您可以执行以下操作:

Adapting an 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,您可以执行以下操作:

Adapting an 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 的 RoleVoterRoleHierarchyVoter 的扩展版本已配置 RoleHierarchy,它从中获取用户被分配的所有“可访问权限”。典型的配置可能如下所示:

Hierarchical Roles Configuration
  • 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,直到 [role="bare"][role="bare"]https://github.com/spring-projects/spring-security/issues/12783 完成。

此示例中,层次结构中有四个角色 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST。使用 ROLE_ADMIN 认证过的用户在针对适应于调用上述 RoleHierarchyVoterAuthorizationManager 评估安全约束时将表现为拥有所有四个角色。> 符号意为“包含”。

角色层次结构提供了一种简化应用程序的访问控制配置数据和/或减少需要分配给用户权限数目的便捷手段。对于更复杂的要求,您可能希望在您的应用程序要求的特定访问权限和分配给用户的角色之间定义逻辑映射,并在加载用户信息时在两者之间进行转换。

Legacy Authorization Components

Spring Security包含一些旧版组件。由于其尚未被移除,因此包含文档以供参考。建议的替代方法如上。

The AccessDecisionManager

AccessDecisionManagerAbstractSecurityInterceptor 调用,负责做出最终的访问控制决策。AccessDecisionManager 接口包含三个方法:

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManagerdecide 方法传递了它做出授权决策所需的所有相关信息。特别是,传递安全的 Object 可以检查实际安全对象调用中包含的自变量。例如,假设安全对象是 MethodInvocation。您可以查询 MethodInvocation 以获取任何 Customer 自变量,然后在 AccessDecisionManager 中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果拒绝了访问,预计实施会引发 AccessDeniedException

AbstractSecurityInterceptor 会在启动时调用 supports(ConfigAttribute) 方法,以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttributesupports(Class) 方法由安全拦截器实现调用,以确保配置的 AccessDecisionManager 支持安全拦截器呈现的安全对象类型。

Voting-Based AccessDecisionManager Implementations

虽然用户可以实现自己的 AccessDecisionManager 来控制授权的所有方面,但 Spring Security 包含几个基于投票的 AccessDecisionManager 实现。Voting Decision Manager 描述了相关类。

下图显示了 AccessDecisionManager 接口:

access decision voting
Figure 2. Voting Decision Manager

使用这种方法,系统会针对授权决策对一系列 AccessDecisionVoter 实现进行轮询。然后,AccessDecisionManager 根据其对投票的评估决定是否抛出 AccessDeniedException

AccessDecisionVoter 接口有三个方法:

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体实施返回 int,可能的值反映在名为 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTEDAccessDecisionVoter 静态字段中。如果对授权决策没有意见,投票实施将返回 ACCESS_ABSTAIN。如果有意见,则必须返回 ACCESS_DENIEDACCESS_GRANTED

Spring Security 提供了三个具体 AccessDecisionManager 实现来统计选票。ConsensusBased 实现基于非弃权票的共识来授予或拒绝访问。提供的属性可控制在平票事件或所有票均弃权情况下的行为。AffirmativeBased 实现会在收到一个或多个 ACCESS_GRANTED 票时授予访问权限(换句话说,将忽略拒绝票,前提是有至少一个授予票)。与 ConsensusBased 实现类似,有一个参数控制所有投票者弃权时的行为。UnanimousBased 提供程序需要一致的 ACCESS_GRANTED 票才能授予访问权限,忽略弃权票。如果出现任何 ACCESS_DENIED 票,它会拒绝访问。和其他实现类似,有一个参数控制所有投票者弃权时的行为。

您可以实现一个以不同方式统计选票的自定义 AccessDecisionManager。例如,来自特定 AccessDecisionVoter 的选票可能会收到额外的加权,而来自特定选民的拒绝票可能具有否决权效果。

RoleVoter

Spring Security 中最常用的 AccessDecisionVoterRoleVoter,它将配置属性视为角色名称,并投票以在用户已被分配该角色的情况下授予访问权限。

如果任何 ConfigAttributeROLE_ 前缀开头,它都会投票。如果存在一个 GrantedAuthority 返回的 String 表示形式(来自 getAuthority() 方法) 与一个或多个以 ROLE_ 前缀开头的 ConfigAttributes 完全相等,则它会投票以授予访问权限。如果没有与 ROLE_ 开头的任何 ConfigAttribute 完全匹配,RoleVoter 会投票拒绝访问。如果没有 ConfigAttributeROLE_ 开头,投票者会弃权。

AuthenticatedVoter

我们隐式看到的另一个投票者是 AuthenticatedVoter,它可用于区分匿名用户、完全认证用户和记住我的认证用户。许多站点允许在记住我的认证下进行某些有限访问,但要求用户通过登录来确认他们的身份以获得完全访问权限。

当我们使用 IS_AUTHENTICATED_ANONYMOUSLY 属性授予匿名访问时,此属性由 AuthenticatedVoter 处理。有关详细信息,请参见 {security-api-url}org/springframework/security/access/vote/AuthenticatedVoter.html[AuthenticatedVoter].

Custom Voters

您还可以实现自定义 AccessDecisionVoter 并放入所需的任何访问控制逻辑。它可能是特定于应用程序(与业务逻辑相关),也可能是实现某些安全管理逻辑。例如,在 Spring 网站上,您可以找到一个 blog article ,描述了如何使用选民实时拒绝帐户已被暂停的用户的访问。

after invocation
Figure 3. After Invocation Implementation

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