Spring Security中AuthorizationManager简介

Spring Security是Spring 框架的扩展,可以轻松地将常见的安全实践构建到我们的应用程序中。这包括用户身份验证和授权、API 保护等等。

在本教程中,我们将了解 Spring Security 内部的众多部分之一:AuthorizationManager。我们将了解它如何融入更大的 Spring Security 生态系统,以及它如何帮助保护我们的应用程序的各种用例。

什么是Spring Security AuthorizationManager
Spring AuthorizationManager是一个接口,允许我们检查经过身份验证的实体是否有权访问安全资源。 Spring Security 使用AuthorizationManager实例为基于请求、基于方法和基于消息的组件做出最终访问控制决策。

作为背景,Spring Security 有几个关键概念,在了解AuthorizationManager的具体角色之前有助于理解这些概念 :

  • 实体Entity:任何可以向系统发出请求的东西。例如,这可能是人类用户或远程 Web 服务。
  • 身份验证Authentication:验证实体是否是其自称身份的过程。这可以通过用户名/密码、令牌或任意数量的其他方法来实现。
  • 授权Authorization:验证实体是否有权访问资源的过程
  • 资源Resource:系统可供访问的任何信息,例如 URL 或文档
  • 权限Authority:通常称为角色,这是表示实体拥有的权限的逻辑名称。单个实体可能被授予零个或多个权限。

考虑到这些概念,我们可以更深入地研究AuthorizationManager接口。

如何使用AuthorizationManager
AuthorizationManager是一个简单的接口,仅包含两个方法:

  1. AuthorizationDecision check(Supplier<Authentication> authentication, T object);
  2. void verify(Supplier<Authentication> authentication, T object);

这两种方法看起来很相似,因为它们采用相同的参数:

  • authentication身份验证:提供代表发出请求的实体的身份验证对象的供应商。
  • object:被请求的安全对象(根据请求的性质而变化)

然而,每种方法都有不同的目的。
  1. 第一个方法返回一个AuthorizationDecision,它是一个布尔值的简单包装,指示实体是否可以访问安全对象。
  2. 第二种方法不返回任何内容。相反,它只是执行授权检查,如果实体无权访问安全对象,则抛出AccessDeniedException。

AuthorizationManager接口是在Spring Security 5.0中引入的。在此接口之前,授权的主要方法是通过AccessDecisionManager接口。虽然AccessDecisionManager接口在 Spring Security 的最新版本中仍然存在,但它已被弃用,应该避免使用AuthorizationManager。

AuthorizationManager的实现
Spring 提供了多种 AuthorizationManager接口的实现。在下面的部分中,我们将了解其中的几个。

1. AuthenticatedAuthorizationManager
我们要查看的第一个实现是AuthenticatedAuthorizationManager。简而言之,此类仅根据实体是否经过身份验证返回肯定的授权决策。此外,它还支持三个级别的身份验证:

  • 匿名:实体未经身份验证
  • 记住我:该实体已通过身份验证并且正在使用记住的凭据
  • 完全经过身份验证:实体经过身份验证并且不使用记住的凭据

请注意,这是Spring Boot为基于 Web 的应用程序创建的默认AuthorizationManager 。默认情况下,所有端点都将允许访问,无论角色或权限如何,只要它来自经过身份验证的实体即可。


2. AuthoritiesAuthorizationManager
该实现的工作原理与前一个类似,但它可以根据多个授权做出决定。它更适用于需要由多个授权访问资源的复杂应用程序。

考虑一个使用不同角色管理发布流程的博客系统。作者和编辑角色都可以访问用于创建和保存文章的资源。但是,只有编辑角色才能访问用于发布的资源。

3.AuthorityAuthorizationManager
这种实现方式相当简单。它根据实体是否具有特定角色来做出所有授权决定。

对于每个资源只需要一个角色或权限的简单应用,这种实现方式非常有效。例如,它可以很好地保护特定的 URL 集,使其只对具有管理员角色的实体开放。

请注意,该实现将决策权委托给 AuthoritiesAuthorizationManager 的一个实例。这也是 Spring 在自定义 SecurityFilterChain 时调用 hasRole() 或 hasAuthorities() 时使用的实现。

4. RequestMatcherDelegatingAuthorizationManager
该实现实际上并不做出授权决定。相反,它会根据 URL 模式委托给另一个实现,通常是上述管理器类中的一个。

例如,如果我们有一些 URL 是公开的,任何人都可以访问,那么我们可以将这些 URL 委托给一个总是返回肯定授权的无操作实现。然后,我们可以将安全请求委托给 AuthoritiesAuthorizationManager,由它来处理角色检查。

事实上,这正是 Spring 在我们向 SecurityFilterChain 添加新请求匹配器时所做的工作。每次我们配置新的请求匹配器并指定一个或多个所需的角色或授权时,Spring 就会创建该类的新实例以及相应的委托。

5. ObservationAuthorizationManager
我们要了解的最后一个实现是 ObservationAuthorizationManager。该类实际上只是另一种实现的封装,并增加了记录与授权决策相关的指标的功能。只要应用程序中存在有效的 ObservationRegistry,Spring 就会自动使用该实现。

6. 其他实现
值得一提的是,Spring Security 中还有其他一些实现。它们大多与用于确保方法安全的各种 Spring 安全注解有关:

  • SecuredAuthorizationManager -> @Secured
  • PreAuthorizeAuthorizationManager -> @PreAuthorize
  • PostAuthorizeAuthorizationManager -> @PostAuthorize

从本质上讲,我们可以用来确保资源安全的任何 Spring 安全注解都有一个相应的 AuthorityManager 实现。

使用多个AuthorizationManager
在实践中,我们很少只使用AuthorizationManager的单个实例。让我们看一下SecurityFilterChain bean 的示例:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/posts/publish/**").hasRole("EDITOR")
        .requestMatchers("/posts/create/**").hasAnyRole("EDITOR", "AUTHOR")
        .anyRequest().permitAll());
    return http.build();
}

此示例使用五个不同的AuthorizationManager实例:
  • 对hasRole() 的调用创建了一个AuthorityAuthorizationManager实例,该实例又委托给一个新的AuthoritiesAuthorizationManager实例。
  • 对hasAnyRole()的调用还会创建一个AuthorityAuthorizationManager实例,该实例又委托给一个新的AuthoritiesAuthorizationManager实例。
  • 对PermitAll()的调用使用 Spring Security 提供的静态无操作AuthorizationManager,它始终提供积极的授权决策。

具有自己角色的附加请求匹配器以及任何基于方法的注释都将创建附加的AuthorizationManager实例。

使用自定义AuthorizationManager
上面提供的实现足以满足许多应用程序的需要。然而,与 Spring 中的许多接口一样,完全可以创建一个自定义的AuthorizationManager来满足我们的任何需求。

让我们定义一个自定义的AuthorizationManager:

AuthorizationManager<RequestAuthorizationContext> customAuthManager() {
    return new AuthorizationManager<RequestAuthorizationContext>() {
        @Override
        public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
            // make authorization decision
        }
    };
}

然后,我们在自定义SecurityFilterChain时传递此实例:

SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authorize) ->
                    authorize.requestMatchers("/custom/**").access(customAuthManager())
    return http.build();
}

在本例中,我们使用RequestAuthorizationContext做出授权决策。此类提供对底层 HTTP 请求的访问,这意味着我们可以根据 cookie、标头等内容做出决策。我们还可以委托第三方服务、数据库或缓存等结构来做出我们想要的任何类型的授权决策。

结论
在本文中,我们仔细研究了 Spring Security 如何处理授权。我们看到了通用的AuthorizationManager接口以及它的两个方法如何做出授权决策。

我们还看到了该实现的各种实现,以及它们如何在 Spring Security 框架的各个地方使用。

最后,我们创建了一个简单的自定义实现,可用于在应用程序中做出我们需要的任何类型的授权决策。

可以在 GitHub 上获取。