24. Authorization Architecture

24.1 Authorities

正如我们在technical overview中看到的那样,所有Authentication实现都存储了GrantedAuthority个对象的列表。这些代表已授予委托人的权限。 GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,然后在做出授权决策时由AccessDecisionManager读取。

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

String getAuthority();

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

“复杂” GrantedAuthority的示例是一种存储适用于不同 Client 帐号的操作和权限阈值列表的实现。将复杂的GrantedAuthority表示为String将会非常困难,因此getAuthority()方法应返回null。这将向任何AccessDecisionManager表示需要特别支持GrantedAuthority实现,以便理解其内容。

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

24.2 调用前处理

正如我们在Technical Overview章中所看到的那样,Spring Security 提供了拦截器,该拦截器控制对安全对象(例如方法调用或 Web 请求)的访问。 AccessDecisionManager决定是否允许进行调用。

24.2.1 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中实现某种安全性逻辑以确保允许委托人对该 Client 进行操作将很容易。如果访问被拒绝,则预期实现会抛出AccessDeniedException

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

24.2.2 基于投票的 AccessDecisionManager 实现

尽管用户可以实现自己的AccessDecisionManager来控制授权的各个方面,但 Spring Security 包括几种基于投票的AccessDecisionManager实现。 图 24.1,“投票决策 Management 器”说明了相关的类。

图 24.1. 投票决策 Manager

访问决定投票

使用此方法,将根据授权决策轮询一系列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实现将基于未弃权票的共识来授予或拒绝访问权限。提供属性以控制在票数相等或所有票都弃权的情况下的行为。如果收到一个或多个ACCESS_GRANTED投票,则AffirmativeBased实现将授予访问权限(即,如果有至少一个授予投票,则拒绝投票将被忽略)。与ConsensusBased实现类似,有一个参数可以控制所有投票者弃权时的行为。 UnanimousBased提供者希望获得一致的ACCESS_GRANTED投票,才能授予访问权限,而忽略弃权。如果有ACCESS_DENIED票,它将拒绝访问。与其他实现一样,如果所有投票者都弃权,则有一个控制行为的参数。

可以实现自定义AccessDecisionManager,以不同的方式计算票数。例如,来自特定AccessDecisionVoter的选票可能会获得额外的权重,而来自特定选民的拒绝选票可能会产生否决权。

RoleVoter

Spring Security 附带的最常用的AccessDecisionVoter是简单的RoleVoter,它会将配置属性视为简单的角色名称,并投票给用户(如果已为该用户分配了该角色)。

如果任何ConfigAttribute以前缀ROLE_开头,它将投票。如果有GrantedAuthority返回(通过getAuthority()方法通过getAuthority()精确地等于一个或多个ConfigAttributes)的GrantedAuthority表示形式,它将投票授予访问权限。如果没有任何与ROLE_开头的ConfigAttribute完全匹配,则RoleVoter将投票拒绝访问。如果没有ConfigAttributeROLE_开头,则投票者将弃权。

AuthenticatedVoter

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

当我们使用属性IS_AUTHENTICATED_ANONYMOUSLY授予匿名访问权限时,该属性已由AuthenticatedVoter处理。有关更多信息,请参见 Javadoc。

Custom Voters

显然,您还可以实现自定义AccessDecisionVoter,并且可以在其中添加几乎任何所需的访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关),也可能实现某些安全 Management 逻辑。例如,您将在 Spring 网站上找到一个blog article,其中描述了如何使用投票器实时拒绝帐户被暂停的用户的实时访问。

24.3 调用处理后

虽然在进行安全对象调用之前由AbstractSecurityInterceptor调用AccessDecisionManager,但是某些应用程序需要一种方法来修改安全对象调用实际返回的对象。尽管您可以轻松实现自己的 AOP 问题来实现这一点,但 Spring Security 提供了一个方便的钩子,该钩子具有一些与其 ACL 功能集成的具体实现。

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

图 24.2. 调用实现后

after invocation

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

24.4 层次角色

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

使用角色层次结构可让您配置哪些角色(或权限)应包括其他角色。 Spring Security 的RoleVoterRoleHierarchyVoter的扩展版本配置了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 c 评估安全约束时将扮演所有四个角色。可以将>符号视为“包含”。

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