章节 V. 授权

Spring Security 中的高级授权能力是其流行性的最令人信服的原因之一。不管你选择如何认证 —— 无论是使用 Spring Security 提供的机制和提供程序,还是与容器或其他非 Spring Security 认证机构集成 —— 你将发现授权服务可以在应用程序中一致地以及简单的方式使用。

在本部分中,我们将探讨第一部分中介绍的不同 AbstractSecurityInterceptor 实现,然后继续探讨如何使用域访问控制列表来微调授权。

25. 授权体系结构

25.1 权限

正如我们在技术概述中看到的,所有 Authentication 实现都存储了 GrantedAuthority 对象的列表。这些代表了已授予主体的权力。GrantedAuthority 对象由 AuthenticationManager 插入 AutheAuthenticationntication 对象中,稍后在做出授权决策时由 AccessDecisionManager 读取。

GrantedAuthority 是一种只有一种方法的接口:

String getAuthority();

此方法允许 AccessDecisionManager 获得 GrantedAuthority 的精确 String 表示形式。通过将表示返回为 String,大多数 AccessDecisionManager 可以容易地读取 GrantedAuthority。如果 GrantedAuthority 不能精确地表示为 String,则 GrantedAuthority 被认为是复杂的,getAuthority() 必须返回 null。

复杂的 GrantedAuthority 的一个示例是存储应用于不同客户帐户号的操作和权限阈值列表的实现。将这个复杂的 GrantedAuthority 表示为 String 非常困难,因此 getAuthority() 方法应该返回 null。这将向任何 AccessDecisionManager 指示它需要具体支持 GrantedAuthority 实现以便理解其内容。

Spring Security 包括一个具体的 GrantedAuthority 实现,SimpleGrantedAuthority。这允许任何用户指定的 String 被转换成 GrantedAuthority。包含在安全体系结构中的所有 AuthenticationProvider 都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

25.2 预调用处理

正如我们在技术概览章节中看到的,Spring Security 提供了拦截器,用于控制对安全对象(如方法调用或 web 请求)的访问。允许调用继续进行的预调用决策是由 AccessDecisionManager 进行的。

25.2.1 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 方法被传递所有相关信息,以便做出授权决策。特别地,传递安全对象允许检查实际安全对象调用中包含的那些参数。例如,假设安全对象是 MethodInvocation。查询 MethodInvocation 以获得任何 Customer 参数都很容易,然后在 AccessDecisionManager 中实现某种安全逻辑以确保主体被允许对那个客户进行操作。如果访问被拒绝,则实现将抛出 AccessDeniedException。

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

25.2.2 基于投票的 AccessDecisionManager 实现

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

图 25.1. 投票决策管理器

access decision voting

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

AccessDecisionVoter 接口有三个方法:

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体实现返回一个 int 值,其中可能的值反映在 AccessDecisionVoter 静态字段 ACCESS_ABSTAIN、ACCESS_DENIED 和 ACCESS_GRANTED 的值中。如果对授权决策没有意见,则投票实现将返回 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(通过 getAuthority() 方法)返回正好等于一个或多个以前缀 ROLE_ 开头的 ConfigAttributes 的 String 表示,则它将投票授予访问权。如果没有与 ROLE_ 开头的任何配置属性的精确匹配,RoleVoter 将投票拒绝访问。如果没有 ConfigAttribute 以 ROLE_ 开头,投票人将弃权。

AuthenticatedVoter

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

当我们使用 IS_AUTHENTICATED_ANONYMOUSLY 属性来授予匿名访问权时,AuthenticatedVoter 正在处理这个属性。有关此类的更多详细信息请参阅 Javadoc 。

定制 Voter

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

25.3 调用处理之后

在进行安全对象调用之前,AbstractSecurityInterceptor 调用 AccessDecisionManager,但有些应用程序需要修改安全对象调用实际返回的对象的方法。尽管你可以轻松实现你自己的 AOP 关注点来实现这一点,但是 Spring Security 提供了一个方便的钩子,它具有几个与 ACL 能力集成的具体实现。

图 25.2, “调用后实现” 说明了 Spring Security 的 AfterInvocationManager 及其具体实现。

图 25.2. 调用后实现

after invocation

与 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 配置属性实现的。

25.4 层次角色

在应用程序中的特定角色应该自动包含其他角色是常见的要求。例如,在具有 "admin" 和 "user" 角色概念的应用程序中,你可能希望管理员能够完成普通用户能做的所有事情。为了实现这一点,你可以确保所有管理员用户也被分配了 "user" 角色。或者,你可以修改每一个访问约束,它要求 "user" 角色也包含 "admin" 角色。如果在应用程序中有很多不同的角色,这会变得相当复杂。

角色层次结构的使用允许你配置哪些角色(或权限)应该包括其他角色。Spring Security 的 RoleVoter 的扩展版本,RoleHierarchyVoter,配置有 RoleHierarchy,以便获得用户分配的所有可访问权限。典型的配置可能是这样的:

<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
	<constructor-arg ref="roleHierarchy" />
</bean>
<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>

这里,我们在层次结构 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST 中有四个角色。使用 ROLE_ADMIN 进行身份验证的用户,当针对用上述 RoleHierarchyVoter 配置的 AccessDecisionManager 评估安全约束时,将表现得好像具有所有四个角色一样。> 可以被认为是包含的意思。

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