授权 HttpServletRequest

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

Spring Security 允许你在 request 层建立 授权模型。例如,通过 Spring Security,你可以说 /admin 下的所有页面都需要授权,而所有其他页面只需要认证。

默认情况下,Spring Security 要求每个请求都要经过认证。也就是说,任何时候你使用一个 HttpSecurity 实例,都有必要声明你的授权规则。

每当你有一个 HttpSecurity 实例时,你至少应该做:

Use authorizeHttpRequests
  • Java

  • Kotlin

  • Xml

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

这告诉 Spring Security,你的应用程序中的任何端点都需要至少经过 security context 的验证才能允许。

在许多情况下,你的授权规则会比这更复杂,所以请考虑以下使用情况:

了解 Request 授权组件如何工作

本节在 Servlet架构和实现 的基础上,深入探讨了在基于Servlet的应用程序中, 授权 是如何在 request 层面发挥作用的。
authorizationfilter
Figure 1. Authorize HttpServletRequest

默认情况下,AuthorizationFilter 是最后一个

默认情况下,AuthorizationFilterSpring Security filter chain 中的最后一个。这意味着 Spring Security 的 authentication filter漏洞保护 和其他 filter 集成都不需要授权。如果你在 AuthorizationFilter 之前添加自己的 filter,它们也将不需要授权;否则,它们将需要授权。

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

所有的调度(Dispatche)都是授权的

AuthorizationFilter 不仅在每个 request 上运行,而且在每个 dispatch 上运行。这意味着 REQUEST dispatch 需要授权,FORWARDERRORINCLUDE 也需要。

例如, Spring MVC 可以将请求 FORWARD 到一个渲染 Thymeleaf 模板的视图解析器,像这样:

Sample Forwarding Spring MVC Controller
  • 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 dispatche

这个原理的另一个例子是 Spring Boot 如何处理错误。如果容器捕捉到一个异常,比如说像下面这样:

Sample Erroring Spring MVC Controller
  • 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 将把它 dispatch 给 ERROR dispatch。

在这种情况下,授权也会发生两次;一次是授权 /endpoint ,一次是 dispatch error。

出于这个原因,你可能想 允许所有的 ERROR dispatch

延迟的 Authentication 查询

当请求 总是被允许或总是被拒绝 时,这与 authorizeHttpRequests 有关。在这些情况下,Authentication 不被查询,使得请求更快。

授权 Endpoint

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

如果你想要求 /endpoint 只能被具有 USER 权限的终端用户访问,那么你可以这样:

Authorize an Endpoint
  • 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>

正如你所看到的,声明可以被分解为 pattern/rule 对。

AuthorizationFilter 按照列出的顺序处理这些配对,只对请求应用第一个匹配。这意味着,即使 /** 也会与 /endpoint 匹配,但上述规则并没有问题。解读上述规则的方法是 "如果请求是 /endpoint,则要求 USER 权限;否则,只要求认证"。

Spring Security 支持几种模式(pattern)和几种规则(rule);你也可以通过编程创建你自己的每种模式。

一旦授权,你就可以用 Security 的测试支持 按以下方式进行测试:

Test Endpoint Authorization
  • 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 目录下的所有端点。在这种情况下,你可以做如下的事情:

Match with 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 权限;否则,只要求认证"

你也可以从请求中提取路径值,如下图所示:

Authorize and Extract
  • 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的测试支持 按以下方式进行测试:

Test Directory Authorization
  • 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 只匹配路径。如果你想匹配查询参数,你将需要一个自定义请求匹配器(request matcher)。

使用正则表达式进行匹配

Spring Security 支持根据正则表达式匹配请求。如果你想在一个子目录上应用比 ** 更严格的匹配标准,这就会很方便。

例如,考虑一个包含 username 的路径和所有 username 必须是字母数字的规则。你可以使用 RegexRequestMatcher 来尊重这个规则,像这样:

Match with Regex
  • 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方法来匹配规则。这一点很方便的一个地方是通过授予的权限进行授权,比如被授予 readwrite 的权限。

为了要求所有的 GET 都有 read 取权限,所有的 POST 都有 write 权限,你可以这样做:

Match by HTTP Method
  • 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 的测试支持 按以下方式进行测试:

Test Http Method Authorization
  • 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());
}

按 Dispatcher 类型进行匹配

目前在XML中不支持这一功能

如前所述,Spring Security 默认授权了所有 dispatcher 类型。尽管在 REQUEST dispatch 中建立的 security context 会延续到后续的 dispatch 中,但细微的不匹配有时会导致意外的 AccessDeniedException

为了解决这个问题,你可以配置 Spring Security 的 Java 配置,允许像 FORWARDERROR 这样的 dispatcher 类型,像这样:

Example 1. Match by Dispatcher Type
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize(DispatcherType.FORWARD, permitAll)
        authorize(DispatcherType.ERROR, permitAll)
        authorize("/endpoint", permitAll)
        authorize(anyRequest, denyAll)
    }
}

使用自定义的 Matcher

目前在XML中不支持这一功能。

在 Java 配置中,你可以创建自己的 RequestMatcher 并像这样提供给 DSL:

Example 2. Authorize by Dispatcher Type
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 方法。

一旦授权,你就可以用 SSecurity的测试支持 按以下方式进行测试:

Test Custom Authorization
  • 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 - 该请求不需要授权,是一个公共端点;请注意,在这种情况下,永远不会从 session 中检索 Authentication

  • denyAll - 该请求在任何情况下都是不允许的;注意在这种情况下,永远不会从 session 中检索 Authentication

  • hasAuthority - 该请求要求 Authentication 有一个符合给定值的 GrantedAuthority

  • hasRole - hasAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。

  • hasAnyAuthority - 该请求要求 Authentication 有一个符合任何给定值的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。

  • access - 该请求使用这个自定义的 AuthorizationManager 来确定访问权限。

现在已经学会了模式(pattern)、规则(rule),以及如何将它们搭配在一起,你应该能够理解这个更复杂的例子中发生了什么:

Authorize Requests
  • 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 Dispatche,以允许 Spring MVC 渲染视图,允许 Spring Boot 渲染错误。
3 我们指定了多种URL模式,任何用户都可以访问。具体来说,如果URL以 "/resources/" 开头,等于 "/signup",或等于 "/about",任何用户都可以访问一个请求。
4 任何以 "/admin/" 开头的URL将被限制给拥有 "ROLE_ADMIN" 角色的用户。你会注意到,由于我们调用的是 hasRole 方法,我们不需要指定 "ROLE_" 前缀。
5 任何以 "/db/" 开头的URL都需要用户被授予 "db" 权限,并且是 "ROLE_ADMIN"。你会注意到,由于我们使用的是 hasRole 表达式,我们不需要指定 "ROLE_" 前缀。
6 任何还没有被匹配的URL都会被拒绝访问。如果你不想意外地忘记更新你的授权规则,这是一个好的策略。

用 SpEL 表示授权

虽然我们推荐使用具体的 AuthorizationManager,但在某些情况下表达式是必要的,例如 <intercept-url> 或 JSP Taglib。因此,本节将重点讨论这些领域中的示例。

鉴于此,让我们更深入地介绍一下 Spring Security 的 Web Security Authorization SpEL API。

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

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

这首先为你的 SpEL 表达式提供了一套增强的授权字段和方法。下面是对最常见的方法的一个快速概述:

  • permitAll - 该请求不需要授权即可调用;注意,在这种情况下,将不会从 session 中检索 Authentication

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

  • hasAuthority - 请求要求 AuthenticationGrantedAuthority 符合给定值。

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

  • hasAnyAuthority - 请求要 Authentication 具有符合任何给定值的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。

  • hasPermission - 用于对象级授权的 PermissionEvaluator 实例的 hook。

这里是对最常见字段的简要介绍:

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

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

在学习了模式(pattern)、规则(rule)以及如何将它们搭配在一起之后,你应该能够理解在这个更复杂的示例中发生了什么:

Authorize Requests Using 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 patter。具体来说,如果URL以 "/static/" 开头,任何用户都可以访问一个请求。
2 任何以 "/admin/" 开头的 URL 将被限制给拥有 "ROLE_ADMIN" 角色的用户。你会注意到,由于我们调用的是 hasRole 方法,我们不需要指定 "ROLE_" 前缀。
3 任何以 "/db/" 开头的 URL 都需要用户被授予 "db" 权限,并且是 "ROLE_ADMIN"。你会注意到,由于我们使用的是 hasRole 表达式,我们不需要指定 "ROLE_" 前缀。
4 任何还没有被匹配的URL都会被拒绝访问。如果你不想意外地忘记更新你的授权规则,这是一个好的策略。

使用路径(Path)参数

此外,Spring Security 还提供了一种发现路径参数的机制,因此也可以在SpEL表达式中访问这些参数。

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

Authorize Request using SpEL path variable
  • 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 可能如下所示:

Open Policy Agent Authorization Manager
  • 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 中:

Any Request Goes to Remote Service
  • Java

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

	return http.build();
}

赞成 permitAll 而不是 ignoring

当你有静态资源时,配置 filter chain 来忽略这些值是很有诱惑力的。更安全的方法是使用 permitAll 来允许它们,就像这样:

Example 3. Permit Static Resources
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

它更安全,因为即使是静态资源,写入安全 header 也很重要,如果请求被忽略,Spring Security 就无法写入安全 header。

在过去,由于 Spring Security 会在每个请求中查询 session,因此会影响性能。然而,从 Spring Security 6 开始,除非授权规则要求,否则会话不再被 ping。由于现在已经解决了对性能的影响,Spring Security 建议对所有请求至少使用 permitAll

authorizeRequests 迁移

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

AuthorizationFilterHttpServletRequest 提供 授权。它作为 Security Filter 之一被插入到 FilterChainProxy 中。

你可以在声明 SecurityFilterChain 时覆盖默认值。不要使 用 authorizeRequests,而是使用 authorizeHttpRequests,像这样:

Use authorizeHttpRequests
  • Java

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

    return http.build();
}

这在很多方面改进了 authorizeRequests

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

  2. 延迟 Authentication 查询。不再需要对每个请求进行认证查询,而是仅在授权决定需要认证的请求中进行查询。

  3. 支持基于 Bean 的配置。

当使用 authorizeHttpRequests 代替 authorizeRequests 时, AuthorizationFilter 会被使用,而不是 FilterSecurityInterceptor

迁移表达式

在可能的情况下,建议你使用类型安全的授权管理器来代替SpEL。对于Java配置, WebExpressionAuthorizationManager 可帮助迁移传统的 SpEL。

要使用 WebExpressionAuthorizationManager,你可以使用你试图迁移的表达式来构建一个 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) 来引用它们。

如果你不能这样做,你可以配置一个 DefaultHttpSecurityExpressionHandler 的 bean 解析器,并提供给 WebExpressionAuthorizationManager#setExpressionhandler

Security Matchers

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 实现最适合你的应用程序: 如果 classpath 中包含 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 使用 AntPathRequestMatcher 配置 HttpSecurity,使其仅适用于以 /api/ 开头的URL。
3 使用 AntPathRequestMatcher,允许 USER 角色的用户访问以 /user/ 开头的 URL。
4 使用 RegexRequestMatcher,允许具有 ADMIN 角色的用户访问以 /admin/ 开头的URL。
5 使用自定义 RequestMatcher,允许具有 SUPERVISOR 角色的用户访问与 MyCustomRequestMatcher 匹配的 URL。

延伸阅读

既然你已经确保了应用程序请求的安全,那么请考虑 确保其方法的安全。你还可以进一步了解 如何测试你的应用程序,或如何将 Spring Security 与应用程序的其他方面(如 数据层跟踪和度量)集成。