授权 HttpServletRequests

Spring Security 允许您在请求级别对授权进行建模。例如,使用 Spring Security,您可以指定 /admin 下的所有页面都需要一个权限,而所有其他页面只需要身份验证。

默认情况下,Spring Security 要求每个请求都经过身份验证。也就是说,无论何时使用HttpSecurity 实例,都需要声明授权规则。

无论何时拥有 HttpSecurity 实例,至少应该执行以下操作

使用 authorizeHttpRequests
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

这告诉 Spring Security,应用程序中的任何端点都需要安全上下文至少经过身份验证才能允许访问。

在许多情况下,授权规则将比这更复杂,因此请考虑以下用例

了解请求授权组件的工作原理

本节基于Servlet 架构和实现,更深入地探讨了在基于 Servlet 的应用程序中授权如何在请求级别工作。
authorizationfilter
图 1. 授权 HttpServletRequest

AuthorizationFilter 默认情况下是最后一个

AuthorizationFilter 默认情况下是Spring Security 过滤器链中的最后一个。这意味着 Spring Security 的身份验证过滤器漏洞利用保护和其他过滤器集成不需要授权。如果您在 AuthorizationFilter 之前添加了自己的过滤器,它们也不需要授权;否则,它们将需要授权。

这通常在您添加Spring MVC 端点时变得很重要。因为它们是由DispatcherServlet 执行的,并且它位于 AuthorizationFilter 之后,所以您的端点需要包含在 authorizeHttpRequests 中才能被允许

所有调度都已授权

AuthorizationFilter 不仅在每个请求上运行,而且在每个调度上运行。这意味着 REQUEST 调度需要授权,但 FORWARDERRORINCLUDE 也需要授权。

例如,Spring MVC 可以将请求 FORWARD 到一个视图解析器,该解析器渲染 Thymeleaf 模板,如下所示

示例转发 Spring MVC 控制器
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        return "endpoint";
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        return "endpoint"
    }
}

在这种情况下,授权会发生两次;一次用于授权 /endpoint,一次用于转发到 Thymeleaf 以渲染“endpoint”模板。

因此,您可能希望允许所有 FORWARD 调度

另一个示例是Spring Boot 如何处理错误。如果容器捕获到异常,例如以下情况

示例错误 Spring MVC 控制器
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        throw new UnsupportedOperationException("unsupported");
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        throw UnsupportedOperationException("unsupported")
    }
}

那么 Boot 将将其调度到 ERROR 调度。

在这种情况下,授权也会发生两次;一次用于授权 /endpoint,一次用于调度错误。

因此,您可能希望允许所有 ERROR 调度

Authentication 查找被延迟

当请求始终被允许或始终被拒绝时,这在 authorizeHttpRequests 中很重要。在这些情况下,Authentication 不会被查询,从而使请求更快。

授权端点

您可以通过按优先级顺序添加更多规则来配置 Spring Security 以具有不同的规则。

如果您希望 /endpoint 只能被具有 USER 权限的最终用户访问,那么您可以执行以下操作

授权端点
  • Java

  • Kotlin

  • Xml

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests((authorize) -> authorize
			.requestMatchers("/endpoint").hasAuthority("USER")
			.anyRequest().authenticated()
		)
        // ...

	return http.build();
}
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http {
        authorizeHttpRequests {
            authorize("/endpoint", hasAuthority("USER"))
            authorize(anyRequest, authenticated)
        }
	}
	return http.build();
}
<http>
    <intercept-url pattern="/endpoint" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

如您所见,声明可以分解为模式/规则对。

AuthorizationFilter 按列出的顺序处理这些对,只将第一个匹配项应用于请求。这意味着即使 /** 也匹配 /endpoint,上面的规则也不成问题。阅读上面规则的方法是“如果请求是 /endpoint,则需要 USER 权限;否则,只需要身份验证”。

Spring Security 支持多种模式和规则;您也可以以编程方式创建自己的模式和规则。

授权后,您可以使用 Security 的测试支持 以以下方式进行测试

测试端点授权
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}

匹配请求

您已经看到了 两种匹配请求的方法

您看到的第一种方法是最简单的,即匹配任何请求。

第二种方法是通过 URI 模式进行匹配。Spring Security 支持两种 URI 模式匹配语言:Ant(如上所示)和 正则表达式

使用 Ant 匹配

Ant 是 Spring Security 用于匹配请求的默认语言。

您可以使用它来匹配单个端点或目录,甚至可以捕获占位符以供日后使用。您还可以对其进行细化以匹配特定的一组 HTTP 方法。

假设您想要匹配 /endpoint 端点,而不是 /resource 目录下的所有端点。在这种情况下,您可以执行以下操作

使用 Ant 匹配
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/**").hasAuthority("USER")
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/**", hasAuthority("USER"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/**" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

这样理解:如果请求是 /resource 或其子目录,则需要 USER 权限;否则,只需要身份验证。

您还可以从请求中提取路径值,如下所示

授权和提取
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

授权后,您可以使用 Security 的测试支持 以以下方式进行测试

测试目录授权
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}
Spring Security 仅匹配路径。如果您想匹配查询参数,则需要自定义请求匹配器。

使用正则表达式匹配

Spring Security 支持将请求与正则表达式进行匹配。如果您想对子目录应用比 ** 更严格的匹配条件,这将非常有用。

例如,考虑一个包含用户名且所有用户名必须是字母数字的路径。您可以使用 RegexRequestMatcher 来遵守此规则,如下所示

使用正则表达式匹配
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

按 HTTP 方法匹配

您也可以按 HTTP 方法匹配规则。这在通过授予的权限进行授权时非常有用,例如授予 `read` 或 `write` 权限。

要要求所有 `GET` 请求具有 `read` 权限,所有 `POST` 请求具有 `write` 权限,您可以执行以下操作

按 HTTP 方法匹配
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(HttpMethod.GET).hasAuthority("read")
        .requestMatchers(HttpMethod.POST).hasAuthority("write")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(HttpMethod.GET, hasAuthority("read"))
        authorize(HttpMethod.POST, hasAuthority("write"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url http-method="GET" pattern="/**" access="hasAuthority('read')"/>
    <intercept-url http-method="POST" pattern="/**" access="hasAuthority('write')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

这些授权规则应读作:“如果请求是 GET,则需要 `read` 权限;否则,如果请求是 POST,则需要 `write` 权限;否则,拒绝请求”。

默认情况下拒绝请求是一种健康的安全性实践,因为它将规则集变成了允许列表。

授权后,您可以使用 Security 的测试支持 以以下方式进行测试

测试 HTTP 方法授权
  • Java

@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isForbidden());
}

@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
    this.mvc.perform(post("/any").with(csrf()))
        .andExpect(status().isOk());
}

@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
    this.mvc.perform(get("/any").with(csrf()))
        .andExpect(status().isForbidden());
}

按调度程序类型匹配

此功能目前在 XML 中不受支持

如前所述,Spring Security 默认情况下授权所有调度程序类型。即使 在 `REQUEST` 调度上建立的安全上下文 会延续到后续调度,但细微的不匹配有时会导致意外的 `AccessDeniedException`。

为了解决这个问题,您可以配置 Spring Security Java 配置以允许 `FORWARD` 和 `ERROR` 等调度程序类型,如下所示

示例 1. 按调度程序类型匹配
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
        authorize("/endpoint", permitAll)
        authorize(anyRequest, denyAll)
    }
}

使用 MvcRequestMatcher

一般来说,您可以使用 `requestMatchers(String)`,如上所示。

但是,如果您将 Spring MVC 映射到不同的 servlet 路径,则需要在安全配置中考虑这一点。

例如,如果 Spring MVC 映射到 `/spring-mvc` 而不是 `/`(默认值),那么您可能有一个像 `/spring-mvc/my/controller` 这样的端点,您希望对其进行授权。

您需要使用 `MvcRequestMatcher` 在您的配置中将 servlet 路径和控制器路径分开,如下所示

示例 2. 按 MvcRequestMatcher 匹配
Java
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
	return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
}

@Bean
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
	http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
            .anyRequest().authenticated()
        );

	return http.build();
}
Kotlin
@Bean
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
    MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");

@Bean
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
    http {
        authorizeHttpRequests {
            authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
            authorize(anyRequest, authenticated)
        }
    }
Xml
<http>
    <intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

这种需求至少可以通过两种不同的方式出现

  • 如果您使用 `spring.mvc.servlet.path` Boot 属性将默认路径(`/`)更改为其他路径

  • 如果您注册了多个 Spring MVC `DispatcherServlet`(因此需要其中一个不是默认路径)

使用自定义匹配器

此功能目前在 XML 中不受支持

在 Java 配置中,您可以创建自己的 RequestMatcher 并将其提供给 DSL,如下所示

示例 3. 按调度程序类型授权
Java
RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(printview).hasAuthority("print")
        .anyRequest().authenticated()
    )
Kotlin
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
http {
    authorizeHttpRequests {
        authorize(printview, hasAuthority("print"))
        authorize(anyRequest, authenticated)
    }
}
因为 RequestMatcher 是一个函数式接口,您可以在 DSL 中将其作为 lambda 提供。但是,如果您想从请求中提取值,则需要有一个具体类,因为这需要覆盖一个 default 方法。

授权后,您可以使用 Security 的测试支持 以以下方式进行测试

测试自定义授权
  • Java

@WithMockUser(authorities="print")
@Test
void printWhenPrintAuthorityThenAuthorized() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void printWhenNoPrintAuthorityThenForbidden() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isForbidden());
}

授权请求

一旦请求匹配,您可以通过多种方式对其进行授权 已经看到,例如 permitAlldenyAllhasAuthority

简而言之,以下是 DSL 中内置的授权规则

  • permitAll - 请求不需要授权,是一个公共端点;请注意,在这种情况下,Authentication 从未从会话中检索

  • denyAll - 请求在任何情况下都不允许;请注意,在这种情况下,Authentication 从未从会话中检索

  • hasAuthority - 请求要求 Authentication 具有 一个 GrantedAuthority,该 GrantedAuthority 与给定值匹配

  • hasRole - hasAuthority 的快捷方式,它以 ROLE_ 或配置为默认前缀的任何内容为前缀

  • hasAnyAuthority - 请求要求 Authentication 具有与任何给定值匹配的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的快捷方式,它以 ROLE_ 或配置为默认前缀的任何内容为前缀

  • access - 请求使用此自定义 AuthorizationManager 来确定访问权限

现在您已经了解了模式、规则以及它们如何配对,您应该能够理解这个更复杂的示例中发生了什么

授权请求
  • Java

import static jakarta.servlet.DispatcherType.*;

import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize                                  (1)
            .dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
			.requestMatchers("/static/**", "/signup", "/about").permitAll()         (3)
			.requestMatchers("/admin/**").hasRole("ADMIN")                             (4)
			.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN")))   (5)
			.anyRequest().denyAll()                                                (6)
		);

	return http.build();
}
1 指定了多个授权规则。每个规则都按声明的顺序进行考虑。
2 调度 FORWARDERROR 被允许,以允许 Spring MVC 呈现视图,并允许 Spring Boot 呈现错误
3 我们指定了多个任何用户都可以访问的 URL 模式。具体来说,如果 URL 以 "/static/" 开头、等于 "/signup" 或等于 "/about",则任何用户都可以访问请求。
4 任何以“/admin/”开头的 URL 将被限制为具有“ROLE_ADMIN”角色的用户访问。您会注意到,由于我们调用了hasRole方法,因此不需要指定“ROLE_”前缀。
5 任何以“/db/”开头的 URL 要求用户既被授予“db”权限,又是“ROLE_ADMIN”。您会注意到,由于我们使用的是hasRole表达式,因此不需要指定“ROLE_”前缀。
6 任何尚未匹配的 URL 都被拒绝访问。如果您不想意外忘记更新授权规则,这是一个很好的策略。

使用 SpEL 表达授权

虽然建议使用具体的AuthorizationManager,但在某些情况下,表达式是必要的,例如使用<intercept-url>或 JSP 标签库。因此,本节将重点介绍来自这些领域的示例。

鉴于此,让我们更深入地了解 Spring Security 的 Web 安全授权 SpEL API。

Spring Security 将其所有授权字段和方法封装在一组根对象中。最通用的根对象称为SecurityExpressionRoot,它是WebSecurityExpressionRoot的基础。Spring Security 在准备评估授权表达式时,将此根对象提供给StandardEvaluationContext

使用授权表达式字段和方法

首先,它为您的 SpEL 表达式提供了一组增强的授权字段和方法。以下是最常用方法的简要概述

  • permitAll - 请求不需要任何授权即可调用;请注意,在这种情况下,Authentication永远不会从会话中检索

  • denyAll - 请求在任何情况下都不允许;请注意,在这种情况下,Authentication 从未从会话中检索

  • hasAuthority - 请求要求 Authentication 具有 一个 GrantedAuthority,该 GrantedAuthority 与给定值匹配

  • hasRole - hasAuthority 的快捷方式,它以 ROLE_ 或配置为默认前缀的任何内容为前缀

  • hasAnyAuthority - 请求要求 Authentication 具有与任何给定值匹配的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的快捷方式,它以 ROLE_ 或配置为默认前缀的任何内容为前缀

  • hasPermission - 与您的PermissionEvaluator实例挂钩,用于执行对象级授权

以下是几个最常用的字段

  • authentication - 与此方法调用关联的Authentication实例

  • principal - 与此方法调用关联的Authentication#getPrincipal

现在您已经了解了模式、规则以及它们如何配对,您应该能够理解这个更复杂的示例中发生了什么

使用 SpEL 授权请求
  • Xml

<http>
    <intercept-url pattern="/static/**" access="permitAll"/> (1)
    <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> (2)
    <intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/> (3)
    <intercept-url pattern="/**" access="denyAll"/> (4)
</http>
1 我们指定了一个任何用户都可以访问的 URL 模式。具体来说,如果 URL 以“/static/”开头,则任何用户都可以访问请求。
2 任何以“/admin/”开头的 URL 将被限制为具有“ROLE_ADMIN”角色的用户访问。您会注意到,由于我们调用了hasRole方法,因此不需要指定“ROLE_”前缀。
3 任何以“/db/”开头的 URL 要求用户既被授予“db”权限,又是“ROLE_ADMIN”。您会注意到,由于我们使用的是hasRole表达式,因此不需要指定“ROLE_”前缀。
4 任何尚未匹配的 URL 都被拒绝访问。如果您不想意外忘记更新授权规则,这是一个很好的策略。

使用路径参数

此外,Spring Security 提供了一种机制来发现路径参数,以便它们也可以在 SpEL 表达式中访问。

例如,您可以通过以下方式在 SpEL 表达式中访问路径参数

使用 SpEL 路径变量授权请求
  • Xml

<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

此表达式引用 /resource/ 之后的路径变量,并要求它等于 Authentication#getName

使用授权数据库、策略代理或其他服务

如果您想将 Spring Security 配置为使用单独的服务进行授权,您可以创建自己的 AuthorizationManager 并将其与 anyRequest 匹配。

首先,您的 AuthorizationManager 可能看起来像这样

开放策略代理授权管理器
  • Java

@Component
public final class OpenPolicyAgentAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        // make request to Open Policy Agent
    }
}

然后,您可以通过以下方式将其连接到 Spring Security

任何请求都转到远程服务
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
	http
		// ...
		.authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(authz)
		);

	return http.build();
}

优先使用 permitAll 而不是 ignoring

当您有静态资源时,您可能会倾向于将过滤器链配置为忽略这些值。更安全的方法是使用 permitAll 允许它们,如下所示

示例 4. 允许静态资源
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

它更安全,因为即使是静态资源,编写安全标头也很重要,而如果请求被忽略,Spring Security 无法做到这一点。

过去,这会带来性能权衡,因为 Spring Security 会在每个请求上查询会话。但是,从 Spring Security 6 开始,除非授权规则需要,否则不再 ping 会话。由于现在解决了性能影响,Spring Security 建议至少对所有请求使用 permitAll

authorizeRequests 迁移

AuthorizationFilter 取代了 FilterSecurityInterceptor。为了保持向后兼容性,FilterSecurityInterceptor 仍然是默认值。本节讨论 AuthorizationFilter 的工作原理以及如何覆盖默认配置。

AuthorizationFilterHttpServletRequest 提供 授权。它被插入到 FilterChainProxy 中,作为 安全过滤器 之一。

当您声明 SecurityFilterChain 时,您可以覆盖默认值。不要使用 authorizeRequests,而是使用 authorizeHttpRequests,如下所示

使用 authorizeHttpRequests
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated();
        )
        // ...

    return http.build();
}

这在许多方面改进了 authorizeRequests

  1. 使用简化的 AuthorizationManager API,而不是元数据源、配置属性、决策管理器和投票者。这简化了重用和定制。

  2. 延迟 Authentication 查找。它不会在每个请求中查找身份验证,而是在授权决策需要身份验证的请求中查找。

  3. 基于 Bean 的配置支持。

当使用 authorizeHttpRequests 而不是 authorizeRequests 时,将使用 AuthorizationFilter 而不是 FilterSecurityInterceptor

迁移表达式

在可能的情况下,建议您使用类型安全的授权管理器,而不是 SpEL。对于 Java 配置,WebExpressionAuthorizationManager 可用于帮助迁移旧的 SpEL。

要使用 WebExpressionAuthorizationManager,您可以使用要迁移的表达式构造一个,如下所示

  • Java

  • Kotlin

.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
.requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))

如果您在表达式中引用了一个 bean,例如:@webSecurity.check(authentication, request),建议您直接调用该 bean,这将类似于以下内容

  • Java

  • Kotlin

.requestMatchers("/test/**").access((authentication, context) ->
    new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
.requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
    AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))

对于包含 bean 引用和其他表达式的复杂指令,建议您将它们更改为实现 AuthorizationManager 并通过调用 .access(AuthorizationManager) 来引用它们。

如果您无法做到这一点,您可以使用 bean 解析器配置一个 DefaultHttpSecurityExpressionHandler,并将其提供给 WebExpressionAuthorizationManager#setExpressionhandler

安全匹配器

RequestMatcher 接口用于确定请求是否与给定规则匹配。我们使用 securityMatchers 来确定 给定的 HttpSecurity 是否应该应用于给定的请求。同样,我们可以使用 requestMatchers 来确定应该应用于给定请求的授权规则。请看以下示例

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher("/api/**")                            (1)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers("/user/**").hasRole("USER")       (2)
				.requestMatchers("/admin/**").hasRole("ADMIN")     (3)
				.anyRequest().authenticated()                      (4)
			)
			.formLogin(withDefaults());
		return http.build();
	}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher("/api/**")                                           (1)
            authorizeHttpRequests {
                authorize("/user/**", hasRole("USER"))                           (2)
                authorize("/admin/**", hasRole("ADMIN"))                         (3)
                authorize(anyRequest, authenticated)                             (4)
            }
        }
        return http.build()
    }

}
1 配置 HttpSecurity 仅应用于以 /api/ 开头的 URL
2 允许具有 USER 角色的用户访问以 /user/ 开头的 URL
3 允许具有 ADMIN 角色的用户访问以 /admin/ 开头的 URL
4 任何不匹配上述规则的其他请求都需要身份验证

securityMatcher(s)requestMatcher(s) 方法将决定哪个 RequestMatcher 实现最适合您的应用程序:如果 Spring MVC 在类路径中,则将使用 MvcRequestMatcher,否则将使用 AntPathRequestMatcher。您可以阅读有关 Spring MVC 集成的更多信息 这里

如果您想使用特定的RequestMatcher,只需将实现传递给securityMatcher和/或requestMatcher方法。

  • Java

  • Kotlin

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; (1)
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher(antMatcher("/api/**"))                              (2)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers(antMatcher("/user/**")).hasRole("USER")         (3)
				.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN")     (4)
				.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR")     (5)
				.anyRequest().authenticated()
			)
			.formLogin(withDefaults());
		return http.build();
	}
}

public class MyCustomRequestMatcher implements RequestMatcher {

    @Override
    public boolean matches(HttpServletRequest request) {
        // ...
    }
}
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher (1)
import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher

@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher(antMatcher("/api/**"))                               (2)
            authorizeHttpRequests {
                authorize(antMatcher("/user/**"), hasRole("USER"))               (3)
                authorize(regexMatcher("/admin/**"), hasRole("ADMIN"))           (4)
                authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR"))       (5)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

}
1 AntPathRequestMatcherRegexRequestMatcher导入静态工厂方法以创建RequestMatcher实例。
2 配置HttpSecurity,使其仅应用于以/api/开头的 URL,使用AntPathRequestMatcher
3 允许具有USER角色的用户访问以/user/开头的 URL,使用AntPathRequestMatcher
4 允许具有ADMIN角色的用户访问以/admin/开头的 URL,使用RegexRequestMatcher
5 允许具有SUPERVISOR角色的用户访问与MyCustomRequestMatcher匹配的 URL,使用自定义的RequestMatcher

进一步阅读

现在您已经保护了应用程序的请求,请考虑保护其方法。您还可以进一步阅读有关测试应用程序或将 Spring Security 与应用程序的其他方面(如数据层跟踪和指标)集成的信息。