Authorize HttpServletRequests

默认情况下,Spring Security 要求对每个请求进行身份验证。但是,每个请求都会通过 AuthorizationFilter 过滤器,该过滤器从 SecurityContextHolder 获取 Authentication,并将其与 HttpServletRequest 一起传递给 AuthorizationManager。

AuthorizationManager 将请求与 authorizeHttpRequests 中的模式进行匹配,并运行相应的规则。如果拒绝授权,则发布 AuthorizationDeniedEvent,并抛出 AccessDeniedException。

授权后,可以使用 @WithMockUser 来测试授权。

你可以通过按优先级顺序添加更多规则来配置 Spring Security,让其具有不同的规则。例如,你可以声明只有拥有 USER 权限的最终用户才能访问 `/endpoint”。

Spring Security 支持多种模式和多种规则,包括匹配任何请求、按 URI 模式匹配、按 HTTP 方法匹配、按 Dispatcher 类型匹配以及使用自定义匹配器。

拒绝请求是良好的安全实践,因为它将规则集转换为允许列表。

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

为了提高安全性,建议使用 permitAll 而不要忽略请求,即使对于静态资源也是如此。

Spring Security 允许您在请求级别 model your authorization。例如,使用 Spring Security,您可以说 `/admin`下的所有页面都需要一个权限,而所有其他页面只需进行身份验证。 默认情况下,Spring Security 要求对每个请求进行身份验证。也就是说,任何时候使用 “@36”,都有必要声明您的授权规则。

每当您有 HttpSecurity 实例时,您至少应该执行以下操作:

Use authorizeHttpRequests
  • Java

  • Kotlin

  • Xml

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

这告诉弹簧安全性,您应用程序中的任何端点至少需要安全上下文经过认证才能允许。 在很多情况下,您的授权规则会比这更加复杂,因此请考虑以下用例:

Understanding How Request Authorization Components Work

本节以 Servlet Architecture and Implementation 为基础,深入探究 authorization 在基于 Servlet 的应用程序中的请求级别工作方式。

authorizationfilter
Figure 1. Authorize HttpServletRequest

AuthorizationFilter Is Last By Default

“@37” 默认在 “@39” 中排在最后。这意味着 Spring Security 的 “@40”、 “@41” 和其他过滤器集成不需要授权。如果您在 “@38” 之前添加自己的过滤器,它们也不需要授权;否则,它们会需要授权。

这通常变得重要的一个地方是当您添加 {spring-framework-reference-url}web.html#spring-web[Spring MVC] 端点时。因为它们由 {spring-framework-reference-url}web.html#mvc-servlet[DispatcherServlet] 执行,并且这在 AuthorizationFilter`之后,所以您的端点需要 included in `authorizeHttpRequests to be permitted

All Dispatches Are Authorized

AuthorizationFilter 不仅在每个请求上运行,还会在每次分派上运行。这意味着 REQUEST 分派需要授权,FORWARDERRORINCLUDE 也需要授权。

例如,{spring-framework-reference-url}web.html#spring-web[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 以渲染“端点”模板。

出于这个原因,您可能希望 permit all FORWARD dispatches

此原则的另一个示例是 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 会将其分派到 ERROR 分派。

在这种情况下,授权也会发生两次;一次是授权 /endpoint,另一次是分派错误。

因此,你可能想要 permit all ERROR dispatches

Authentication Lookup is Deferred

记住 “@44”。

当请求 “@47” 时,这很重要。在这些情况下,不会查询 “@48”,从而使请求更快。

Authorizing an Endpoint

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

如果您希望只允许拥有 USER 权限的最终用户访问 /endpoint,您可以执行以下操作:

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>

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

AuthorizationFilter 以所列顺序处理这些对,仅对请求应用第一个匹配项。这意味着,即使 /** 也将对 /endpoint 进行匹配,上面的规则也没有问题。阅读上述规则的方式为“如果请求是 /endpoint,则需要 USER 权限;否则,仅需要身份验证”。

Spring Security 支持多种模式和多种规则;您还可以以编程方式创建每种模式或规则。

获得授权后,您可以使用 “@49” 以以下方式对其进行测试:

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());
}

Matching Requests

上面您已经看到了 two ways to match requests

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

第二个是按 URI 模式匹配。Spring Security 支持两种 URI 模式匹配语言:Ant(如上所示)和 Regular Expressions

Matching Using 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>

获得授权后,您可以使用 “@49” 以以下方式对其进行测试:

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 仅匹配路径。如果您想匹配查询参数,则需要自定义请求匹配器。

Matching Using Regular Expressions

Spring Security 支持针对正则表达式匹配请求。如果您想对子目录应用比 ** 更严格的匹配标准,这可能会派上用场。

例如,考虑一条包含用户名且规则是所有用户名必须为字母数字的路径。您可以使用 {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[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>

Matching By Http Method

您还可以按 HTTP 方法匹配规则。当根据授予的权限授权(如被授予 readwrite 权限)时,此方法非常有用。

要要求所有 GET`s to have the `read 权限和所有 POST`s to have the `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 权限;否则,拒绝请求”

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

获得授权后,您可以使用 “@49” 以以下方式对其进行测试:

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());
}

Matching By Dispatcher Type

该特性当前在 XML 中不受支持

如前所述,Spring Security “@52”。尽管在 “@50” 调度上建立的 “@53” 延续到后续调度,但有时细微的不匹配会导致意外的 “@51”。

为了解决此问题,您可以配置 Spring Security Java 配置以允许 FORWARDERROR 等调度类型,如下所示:

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(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
        authorize("/endpoint", permitAll)
        authorize(anyRequest, denyAll)
    }
}

Using an MvcRequestMatcher

一般而言,你可以像上面展示的那样使用`requestMatchers(String)`。

然而,如果你将 Spring MVC 映射到一个不同的 servlet 路径,那么你需要在安全配置中考虑这一点。

例如,如果 Spring MVC 映射到 /spring-mvc 而不是 /(默认),那么你可能有一个端点,比如 /spring-mvc/my/controller,你想授权它。

你需要使用 MvcRequestMatcher 来分割你的配置中的 servlet 路径和控制器路径,就像这样:

Example 2. Match by 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 引导属性将默认路径 (/ ) 更改为其他内容

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

Using a Custom Matcher

该特性当前在 XML 中不受支持

在 Java 配置中,您可以创建自己的 {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[RequestMatcher],并像这样供应 DSL:

Example 3. 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)
    }
}

由于 {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[RequestMatcher] 是一个函数式接口,因此你可以将其作为 DSL 中的 Lambda 表达式提供。但是,如果你希望从请求中提取值,你将需要有具体的类,因为这要求覆盖 default 方法。

获得授权后,您可以使用 “@49” 以以下方式对其进行测试:

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());
}

Authorizing Requests

一旦请求匹配,你就可以通过多种方式授权它,比如 already seenpermitAlldenyAllhasAuthority

简要总结一下,以下是内置于 DSL 的授权规则:

  • permitAll - 该请求不需要授权并且是公共端点;请注意,在这种情况下,永远不会从会话中检索 the Authentication

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

  • hasAuthority - 该请求要求 Authentication 具有与给定值匹配的 a GrantedAuthority

  • @14 - @15 的快捷方式,它添加前缀 @16 或配置为默认前缀的任何内容

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

  • @20 - @21 的快捷方式,它添加前缀 @22 或配置为默认前缀的任何内容

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

现在已经学习了模式、规则以及它们如何组合在一起,你应该能够理解此更复杂的示例中发生的事情:

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 Dispatches FORWARDERROR 允许 {spring-framework-reference-url}web.html#spring-web[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,都会拒绝访问。如果你不想忘记意外更新授权规则,这是一个好策略。

Expressing Authorization with SpEL

尽管建议使用具体的 AuthorizationManager,但有时需要表达式,例如使用 <intercept-url> 或 JSP Taglibs。因此,本节将重点介绍来自这些领域的示例。

考虑到这一点,我们来更深入地了解一下 Spring Security 的 Web Security Authorization SpEL API。

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

Using Authorization Expression Fields and Methods

此提供的第一个内容是为 SpEL 表达式提供了一组增强型授权字段和方法。以下是常用方法的快速概览:

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

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

  • hasAuthority - 该请求要求 Authentication 具有与给定值匹配的 a GrantedAuthority

  • @14 - @15 的快捷方式,它添加前缀 @16 或配置为默认前缀的任何内容

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

  • @20 - @21 的快捷方式,它添加前缀 @22 或配置为默认前缀的任何内容

  • @23 - 用于执行对象级别授权,您可以挂接到 @24 实例

以下是常用字段的简要介绍:

  • @25 - 与此方法调用相关联的 @26 实例

  • @27 - 与此方法调用相关联的 @28

现在已经学习了模式、规则以及它们如何组合在一起,你应该能够理解此更复杂的示例中发生的事情:

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 模式。具体来说,如果 URL 以“/static/”开头,则任何用户都可以访问请求。
2 以“/admin/”开头的任何 URL 将限制为具有“ROLE_ADMIN”角色的用户。你将注意到,由于我们要调用 hasRole 方法,我们无需指定“ROLE_”前缀。
3 以“/db/”开头的任何 URL 要求用户既已获得“db”权限,又具有“ROLE_ADMIN”身份。你将注意到,由于我们要使用 hasRole 表达式,我们无需指定“ROLE_”前缀。
4 对于尚未匹配到的任何 URL,都会拒绝访问。如果你不想忘记意外更新授权规则,这是一个好策略。

Using Path Parameters

此外,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

Use an Authorization Database, Policy Agent, or Other Service

如果你想配置 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();
}

Favor permitAll over ignoring

当你有静态资源时,很容易将过滤器链配置为忽略这些值。一种更安全的做法是使用 permitAll 允许它们,就像这样:

Example 4. Permit Static Resources
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

Migrating from authorizeRequests

AuthorizationFilter 取代了 {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[FilterSecurityInterceptor]。为保持向后兼容,FilterSecurityInterceptor 仍然是默认设置。本节讨论了 AuthorizationFilter 的工作方式以及如何覆盖默认配置。

{security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[@54] 提供 “HttpServletRequest” 的 “@55”。它被插入到 “@56” 中,作为 “@57” 之一。

在声明 SecurityFilterChain 时,您可以覆盖默认设置。不要使用 {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[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,而不是元数据源、配置属性、决策管理器和投票者。这可简化复用和自定义。

  2. 延迟 Authentication 查找。身份验证无需针对每个请求进行查找,而只会针对授权决策需要身份验证的请求进行查找。

  3. Bean-based configuration support.

使用 authorizeHttpRequests 代替 authorizeRequests 时,将使用 {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[AuthorizationFilter] 代替 {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[FilterSecurityInterceptor].

Migrating Expressions

在可能的情况下,建议您使用类型安全授权管理器,而不是 SpEL。对于 Java 配置,{security-api-url}org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.html[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 解析器配置 {security-api-url}org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.html[DefaultHttpSecurityExpressionHandler],然后将其提供给 WebExpressionAuthorizationManager#setExpressionhandler.

Security Matchers

{security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[@58] 接口用于确定请求是否匹配给定规则。我们使用 “@59” 来确定是否应将 “@62” 应用到给定请求。同样,我们可以使用 “@61” 来确定我们应将哪些授权规则应用到给定请求。请看以下示例:

  • 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-framework-reference-url}web.html#spring-web[Spring MVC] 在类路径中,则 {security-api-url}org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.html[MvcRequestMatcher] 将被使用,否则 {security-api-url}org/springframework/security/web/servlet/util/matcher/AntPathRequestMatcher.html[AntPathRequestMatcher] 将被使用。您可以详细了解 Spring MVC 集成 here.

如果你想使用特定的 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

Further Reading

既然您已经保护了应用程序的请求,请考虑 securing its methods.您还可以进一步阅读有关 testing your application 或将 Spring Security 与应用程序的其他方面(如 the data layertracing and metrics)集成的内容。