# 授权体系结构

## 当局

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

`GrantedAuthority`是一个只有一个方法的接口：

```
String getAuthority();
```

这种方法允许`AuthorizationManager`s 得到精确的`String`表示的`GrantedAuthority`。通过以`String`的形式返回表示，大多数`AuthorizationManager`s 和`AccessDecisionManager`s 都可以轻松地“读取”`GrantedAuthority`s。如果`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`同时取代[`AccessDecisionManager`和`AccessDecisionVoter`]（#authz-legacy-note）。

鼓励定制`AccessDecisionManager`或`AccessDecisionVoter`的应用程序[更改为使用`AuthorizationManager`]（#authz-voter-adaption）。

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

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

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

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

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

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

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

`RequestMatcherDelegatingAuthorizationManager`将请求与最合适的委托匹配`AuthorizationManager`。对于方法安全性，可以使用`AuthorizationManagerBeforeMethodInterceptor`和`AuthorizationManagerAfterMethodInterceptor`。

[授权管理器实现](#authz-authorization-manager-implementations)说明了相关的类。

![授权层次结构](../../_images/servlet/authorization/authorizationhierarchy.png)

图 1。授权管理器实现

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

#### AuthorityAuthorizationManager

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

#### AuthenticatedauthorizationManager

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

#### 自定义授权管理器

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

|   |你将在 Spring 网站上找到[博客文章](https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time)，其中描述了如何使用遗留的`AccessDecisionVoter`来实时拒绝帐户已被暂停的用户的访问。<br/>相反，你可以通过实现`AuthorizationManager`来实现相同的结果。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 适应 accessisodecisionmanager 和 accessidecisionvoters

在`AuthorizationManager`之前， Spring 发布了安全性[`AccessDecisionManager`和`AccessDecisionVoter`]（#authz-legacy-note）。

在某些情况下，例如迁移较旧的应用程序，可能需要引入一个调用`AuthorizationManager`或`AccessDecisionVoter`的`AuthorizationManager`。

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

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

## 遗留授权组件

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

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

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

### 基于投票的 AccessDecisionManager 实现

虽然用户可以实现他们自己的`AccessDecisionManager`以控制授权的所有方面， Spring 安全性包括几个基于投票的`AccessDecisionManager`实现。[投票决策经理](#authz-access-voting)举例说明了相关的类。

![访问决定投票](../../_images/servlet/authorization/access-decision-voting.png)

图 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_ABSTAIN`、`ACCESS_DENIED`和`ACCESS_GRANTED`中。如果一个投票实现对授权决定没有意见，它将返回`ACCESS_ABSTAIN`。如果它确实有意见，它必须返回`ACCESS_DENIED`或`ACCESS_GRANTED`。

有三个具体的`AccessDecisionManager`s 提供了 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`将投票拒绝访问。如果不`ConfigAttribute`以`ROLE_`开头，则投票人将弃权。

#### 已验证的投票人

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

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

#### 习俗投票人

显然，你还可以实现一个自定义`AccessDecisionVoter`，并且你可以在其中放入你想要的任何访问控制逻辑。它可能是特定于你的应用程序的（与业务逻辑相关的），或者它可能实现一些安全管理逻辑。例如，你将在 Spring 网站上找到一个[博客文章](https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time)，该网站描述了如何使用投票器实时拒绝帐户已被暂停的用户的访问。

![调用后](../../_images/servlet/authorization/after-invocation.png)

图 3。调用实现之后

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

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

[授权](index.html)[授权 HTTP 请求](authorize-http-requests.html)

