# 授权体系结构

# 当局

[Authentication](../authentication/architecture.html# Servlet-authentication-authentication),讨论了所有Authentication实现如何存储GrantedAuthority对象的列表。这些代表已授予校长的权力。GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,然后在做出授权决策时由AuthorizationManager读取。

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

String getAuthority();

这种方法允许AuthorizationManagers 得到精确的String表示的GrantedAuthority。通过以String的形式返回表示,大多数AuthorizationManagers 和AccessDecisionManagers 都可以轻松地“读取”GrantedAuthoritys。如果GrantedAuthority不能精确地表示为String,则GrantedAuthority被认为是“复杂的”,而getAuthority()必须返回null

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

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

# 调用前处理

Spring 安全性提供了拦截器,该拦截器控制对安全对象的访问,例如方法调用或 Web 请求。关于是否允许调用继续进行的调用前决定由AccessDecisionManager做出。

# 授权经理

AuthorizationManager同时取代[AccessDecisionManagerAccessDecisionVoter](#authz-legacy-note)。

鼓励定制AccessDecisionManagerAccessDecisionVoter的应用程序[更改为使用AuthorizationManager](#authz-voter-adaption)。

AuthorizationManagers 由[AuthorizationFilter]调用,并负责做出最终的访问控制决策。AuthorizationManager接口包含两种方法:

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

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

将传递AuthorizationManagercheck方法所需的所有相关信息,以便做出授权决定。特别地,传递 SecureObject使实际安全对象调用中包含的那些参数能够被检查。例如,假设安全对象是MethodInvocation。对于任何Customer参数,都可以很容易地查询MethodInvocation,然后在AuthorizationManager中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果授予访问,则预期实现将返回正的,如果拒绝访问,则返回负的,并且在不做出决定时返回空的。

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

# 基于委托的授权管理器实现

虽然用户可以实现他们自己的AuthorizationManager来控制授权的所有方面, Spring 安全性提供了一个委托AuthorizationManager,它可以与单个AuthorizationManagers 协作。

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

授权管理器实现说明了相关的类。

授权层次结构

图 1。授权管理器实现

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

# AuthorityAuthorizationManager

Spring 安全性提供的最常见的AuthorizationManagerAuthorityAuthorizationManager。它配置了一组给定的权限,以便在当前Authentication上查找。如果Authentication包含任何配置的权限,它将返回正的AuthorizationDecision。否则它将返回负值AuthorizationDecision

# AuthenticatedauthorizationManager

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

# 自定义授权管理器

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

你将在 Spring 网站上找到博客文章 (opens new window),其中描述了如何使用遗留的AccessDecisionVoter来实时拒绝帐户已被暂停的用户的访问。
相反,你可以通过实现AuthorizationManager来实现相同的结果。

# 适应 accessisodecisionmanager 和 accessidecisionvoters

AuthorizationManager之前, Spring 发布了安全性[AccessDecisionManagerAccessDecisionVoter](#authz-legacy-note)。

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

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

例 1。适应 accessisdecisionManager

爪哇

@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<ConfigAttributes> 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<ConfigAttributes> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

然后把它连接到你的SecurityFilterChain

或者只调用AccessDecisionVoter,你可以这样做:

例 2。适应辅助决策投票人

爪哇

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

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttributes> 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 的投票人RoleHierarchyVoter的扩展版本配置了RoleHierarchy,它从该版本获得分配给用户的所有“可访问权限”。一个典型的配置可能是这样的:

例 3。分层角色配置

爪哇

@Bean
AccessDecisionVoter hierarchyVoter() {
    RoleHierarchy hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
            "ROLE_STAFF > ROLE_USER\n" +
            "ROLE_USER > ROLE_GUEST");
    return new RoleHierarcyVoter(hierarchy);
}

XML

<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进行身份验证的用户,在针对适用于调用上述RoleHierarchyVoterAuthorizationManager的安全约束进行评估时,将表现为他们拥有所有四个角色。>符号可以被认为是“包括”的意思。

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

# 遗留授权组件

Spring 安全性包含一些遗留组件。
由于它们尚未被删除,因此出于历史目的而包含文档。
它们的建议替换在上面。

# AccessDecisionManager

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

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

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

将传递AccessDecisionManagerdecide方法所需的所有相关信息,以便做出授权决定。特别地,传递 Secure使那些包含在实际安全对象调用中的参数能够被检查。例如,假设安全对象是MethodInvocation。对于任何Customer参数,都可以很容易地查询MethodInvocation,然后在AccessDecisionManager中实现某种安全逻辑,以确保主体被允许对该客户进行操作。如果访问被拒绝,则期望实现抛出AccessDeniedException

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

# 基于投票的 AccessDecisionManager 实现

虽然用户可以实现他们自己的AccessDecisionManager以控制授权的所有方面, Spring 安全性包括几个基于投票的AccessDecisionManager实现。投票决策经理举例说明了相关的类。

访问决定投票

图 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

有三个具体的AccessDecisionManagers 提供了 Spring 安全,以统计选票。该ConsensusBased实现将基于非弃权投票的共识授予或拒绝访问。在票数相等或所有投票都弃权的情况下,提供属性以控制行为。如果收到一个或多个ACCESS_GRANTED投票,AffirmativeBased实现将授予访问权限(即,如果至少有一个授予投票,则拒绝投票将被忽略)。与ConsensusBased实现类似,如果所有投票者弃权,则有一个参数来控制行为。UnanimousBased提供者期望获得一致的ACCESS_GRANTED票,以授予访问权限,而忽略弃权。如果有任何ACCESS_DENIED投票,它将拒绝访问。与其他实现一样,如果所有投票者都弃权,则有一个控制行为的参数。

可以实现一个自定义AccessDecisionManager,该自定义可以以不同的方式对选票进行统计。例如,来自特定AccessDecisionVoter的投票可能会获得额外的权重,而来自特定选民的拒绝投票可能具有否决效果。

# RoleVoter

Spring 安全性提供的最常用的AccessDecisionVoter是简单的RoleVoter,它将配置属性视为简单的角色名称和投票,以在用户已被分配该角色时授予访问权限。

如果有ConfigAttribute开头的前缀ROLE_,则将进行投票。如果有一个GrantedAuthority返回一个String表示(通过getAuthority()方法)完全等于一个或多个ConfigAttributes,它将投票授予访问权限,该表示从前缀ROLE_开始。如果没有任何以ROLE_开头的ConfigAttribute完全匹配,则RoleVoter将投票拒绝访问。如果不ConfigAttributeROLE_开头,则投票人将弃权。

# 已验证的投票人

我们隐式看到的另一个投票者是AuthenticatedVoter,它可以用来区分匿名的、完全验证的和通过 rememe 验证的用户。许多网站在 Remember-Me 身份验证下允许某些有限的访问,但需要用户通过登录来确认其身份,以获得完全访问权限。

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

# 习俗投票人

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

调用后

图 3。调用实现之后

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

请注意,如果你使用AfterInvocationManager,你仍然需要允许MethodSecurityInterceptorAccessDecisionManager的配置属性来允许操作。如果你使用的是包括AccessDecisionManager实现的典型 Spring 安全性,那么没有为特定的安全方法调用定义配置属性将导致每个AccessDecisionVoter放弃投票。反过来,如果AccessDecisionManager属性“allowifallabstaindecisions”是false,则将抛出一个AccessDeniedException。你可以通过(i)将“AllowifallabstainDecisions”设置为true(尽管通常不建议这样做)或简单地确保至少有一个配置属性(AccessDecisionVoter将投票授予访问权限)来避免这个潜在的问题。后一种(推荐的)方法通常通过ROLE_USERROLE_AUTHENTICATED配置属性来实现。

授权授权 HTTP 请求