授权架构

本节描述了适用于授权的 Spring Security 架构。

权限

Authentication 讨论了所有 Authentication 实现如何存储 GrantedAuthority 对象的列表。这些对象表示授予主体(principal)的权限。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,并在稍后由 AuthorizationManager 实例在做出授权决策时读取。

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。此实现允许将任何用户指定的 String 转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

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

您可以使用 GrantedAuthorityDefaults 进行自定义。GrantedAuthorityDefaults 旨在允许自定义用于基于角色的授权规则的前缀。

您可以通过暴露 GrantedAuthorityDefaults Bean 来配置授权规则以使用不同的前缀,如下所示

自定义 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 类之前发布它

调用处理

Spring Security 提供拦截器来控制对安全对象的访问,例如方法调用或 Web 请求。在调用被允许继续之前,AuthorizationManager 实例会做出预调用决策。同样,AuthorizationManager 实例也会对给定值是否可以返回做出后调用决策。

AuthorizationManager

AuthorizationManager 取代了 AccessDecisionManagerAccessDecisionVoter

建议自定义 AccessDecisionManagerAccessDecisionVoter 的应用程序 更改为使用 AuthorizationManager

AuthorizationManager 被 Spring Security 的基于请求的基于方法的基于消息的授权组件调用,并负责做出最终的访问控制决策。AuthorizationManager 接口包含两个方法

AuthorizationResult authorize(Supplier<Authentication> authentication, Object secureObject);

default void verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

AuthorizationManagercheck 方法被传递了所有做出授权决策所需的相关信息。特别是,传递安全 Object 使得可以检查实际安全对象调用中包含的参数。例如,假设安全对象是 MethodInvocation。可以很容易地查询 MethodInvocation 以获取任何 Customer 参数,然后在 AuthorizationManager 中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果授予访问权限,实现预期会返回一个肯定的 AuthorizationDecision;如果拒绝访问权限,则返回一个否定的 AuthorizationDecision;当不做出决策时,则返回一个空的 AuthorizationDecision

verify 调用 authorize,然后在 AuthorizationDecision 为负的情况下抛出 AccessDeniedException

基于委托的 AuthorizationManager 实现

虽然用户可以实现自己的 AuthorizationManager 来控制授权的所有方面,但 Spring Security 附带了一个委托 AuthorizationManager,它可以与各个 AuthorizationManager 协作。

RequestMatcherDelegatingAuthorizationManager 将请求与最合适的委托 AuthorizationManager 匹配。对于方法安全性,您可以使用 AuthorizationManagerBeforeMethodInterceptorAuthorizationManagerAfterMethodInterceptor

Authorization Manager 实现 演示了相关类。

authorizationhierarchy
图 1. 授权管理器实现

使用这种方法,可以就授权决策对 AuthorizationManager 实现的组合进行轮询。

AuthorityAuthorizationManager

Spring Security 中最常见的 AuthorizationManagerAuthorityAuthorizationManager。它配置了一组给定的权限,以便在当前 Authentication 上查找。如果 Authentication 包含任何已配置的权限,它将返回肯定的 AuthorizationDecision。否则,它将返回否定的 AuthorizationDecision

AuthenticatedAuthorizationManager

另一个管理器是 AuthenticatedAuthorizationManager。它可以用来区分匿名用户、完全认证用户和“记住我”认证用户。许多网站在“记住我”认证下允许某些有限访问,但要求用户通过登录来确认其身份以获得完全访问。

创建 AuthorizationManager 实例

AuthorizationManagerFactory 接口(在 Spring Security 7.0 中引入)用于在基于请求的基于方法的授权组件中创建通用的 AuthorizationManager。以下是 AuthorizationManagerFactory 接口的草图

public interface AuthorizationManagerFactory<T> {
	AuthorizationManager<T> permitAll();
	AuthorizationManager<T> denyAll();
	AuthorizationManager<T> hasRole(String role);
	AuthorizationManager<T> hasAnyRole(String... roles);
	AuthorizationManager<T> hasAllRoles(String... roles);
	AuthorizationManager<T> hasAuthority(String authority);
	AuthorizationManager<T> hasAnyAuthority(String... authorities);
	AuthorizationManager<T> hasAllAuthorities(String... authorities);
	AuthorizationManager<T> authenticated();
	AuthorizationManager<T> fullyAuthenticated();
	AuthorizationManager<T> rememberMe();
	AuthorizationManager<T> anonymous();
}

默认实现是 DefaultAuthorizationManagerFactory,它允许自定义提供给工厂创建的 AuthorizationManagerrolePrefix(默认为 "ROLE_")、RoleHierarchyAuthenticationTrustManager

为了自定义 Spring Security 使用的默认实例,只需发布一个 bean,如以下示例所示

  • Java

  • Kotlin

  • Xml

@Bean
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
	DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
			new DefaultAuthorizationManagerFactory<>();
	authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
	authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
	authorizationManagerFactory.setRolePrefix("role_");

	return authorizationManagerFactory;
}
@Bean
fun <T> authorizationManagerFactory(): AuthorizationManagerFactory<T> {
    val authorizationManagerFactory = DefaultAuthorizationManagerFactory<T>()
    authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver())
    authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy())
    authorizationManagerFactory.setRolePrefix("role_")

    return authorizationManagerFactory
}
<b:bean id="authorizationManagerFactory" class="org.springframework.security.authorization.DefaultAuthorizationManagerFactory">
	<b:property name="trustResolver" ref="authenticationTrustResolver"/>
	<b:property name="roleHierarchy" ref="roleHierarchy"/>
	<b:property name="rolePrefix" value="role_"/>
</b:bean>
还可以通过提供具体的参数化类型而不是泛型类型来在 Spring Security 中定位此工厂的特定用法。请参阅文档的基于请求的基于方法的部分中的每个示例。

除了简单地自定义 AuthorizationManagerFactory 的默认实例之外,您还可以提供自己的实现来完全自定义工厂创建的实例并提供自己的实现。

实际接口为所有工厂方法提供了默认实现,这允许自定义实现只实现需要自定义的方法。

AuthorizationManagers

AuthorizationManagers 中还有一些有用的静态工厂,用于将单个 AuthorizationManager 组合成更复杂的表达式。

自定义授权管理器

显然,您还可以实现自定义的 AuthorizationManager,并且可以在其中放入您想要的任何访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),也可能实现一些安全管理逻辑。例如,您可以创建能够查询 Open Policy Agent 或您自己的授权数据库的实现。

您可以在 Spring 网站上找到一篇博客文章,描述了如何使用旧版 AccessDecisionVoter 实时拒绝帐户已被暂停的用户访问。您可以通过实现 AuthorizationManager 来达到相同的效果。

适配 AccessDecisionManager 和 AccessDecisionVoters

AuthorizationManager 之前,Spring Security 发布了 AccessDecisionManagerAccessDecisionVoter

在某些情况下,例如迁移旧应用程序时,可能需要引入一个调用 AccessDecisionManagerAccessDecisionVoterAuthorizationManager

要调用现有的 AccessDecisionManager,您可以这样做

适配 AccessDecisionManager
  • Java

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationResult authorize(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,您可以这样做

适配 AccessDecisionVoter
  • Java

@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationResult authorize(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 中。

分层角色

应用程序中常见的需求是特定角色应自动“包含”其他角色。例如,在一个具有“admin”和“user”角色概念的应用程序中,您可能希望管理员能够执行普通用户可以执行的所有操作。为此,您可以确保所有管理员用户也被分配了“user”角色。或者,您可以修改所有需要“user”角色的访问约束,使其也包含“admin”角色。如果您的应用程序中有很多不同的角色,这可能会变得非常复杂。

使用角色层次结构允许您配置哪些角色(或权限)应该包含其他角色。这在 HttpSecurity#authorizeHttpRequests 中的基于过滤器的授权和通过 DefaultMethodSecurityExpressionHandler 用于 pre-post 注解、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 包含一些遗留组件。由于它们尚未移除,因此包含文档供历史参考。建议的替代方案在上方。

访问遗留授权组件时,请务必同时包含 spring-security-access 依赖项,如下所示

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-access</artifactId>
</dependency>
implementation('org.springframework.security:spring-security-access')

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

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

基于投票的 AccessDecisionManager 实现

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

下图显示了 AccessDecisionManager 接口

access decision voting
图 2. 投票决策管理器

通过这种方法,对一系列 AccessDecisionVoter 实现进行轮询以做出授权决策。然后 AccessDecisionManager 根据其对投票的评估来决定是否抛出 AccessDeniedException

AccessDecisionVoter 接口有三个方法

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体实现返回一个 int,其可能的值反映在 AccessDecisionVoter 静态字段中,名为 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTED。如果一个投票实现对授权决策没有意见,则返回 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 处理。有关更多信息,请参阅 AuthenticatedVoter

自定义投票者

您还可以实现自定义的 AccessDecisionVoter,并在其中放入您想要的任何访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),也可能实现一些安全管理逻辑。例如,在 Spring 网站上,您可以找到一篇博客文章,描述了如何使用投票者实时拒绝帐户已被暂停的用户访问。

after invocation
图 3. 调用后实现

与 Spring Security 的许多其他部分一样,AfterInvocationManager 有一个单一的具体实现,即 AfterInvocationProviderManager,它轮询 AfterInvocationProvider 列表。每个 AfterInvocationProvider 都被允许修改返回对象或抛出 AccessDeniedException。实际上,多个提供者可以修改对象,因为上一个提供者的结果会传递给列表中的下一个。

请注意,如果您使用 AfterInvocationManager,您仍然需要配置属性以允许 MethodSecurityInterceptorAccessDecisionManager 允许操作。如果您使用典型的 Spring Security 中包含的 AccessDecisionManager 实现,对于特定安全方法调用未定义配置属性将导致每个 AccessDecisionVoter 弃权投票。反过来,如果 AccessDecisionManager 属性“allowIfAllAbstainDecisions”为 false,则将抛出 AccessDeniedException。您可以通过以下两种方式避免此潜在问题:(i) 将“allowIfAllAbstainDecisions”设置为 true(尽管通常不建议这样做),或 (ii) 确保至少有一个配置属性会使 AccessDecisionVoter 投票授予访问权限。后一种(推荐的)方法通常通过 ROLE_USERROLE_AUTHENTICATED 配置属性实现。

© . This site is unofficial and not affiliated with VMware.