授权 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执行,而 DispatcherServletAuthorizationFilter 之后运行,所以你的端点需要包含在 authorizeHttpRequests才能被允许访问。

所有转发都经过授权

AuthorizationFilter 不仅在每个请求上运行,而且在每个转发 (dispatch) 上运行。这意味着 REQUEST 转发需要授权,`FORWARD`、`ERROR` 和 `INCLUDE` 也需要。

例如,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
public SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
	    .requestMatchers("/endpoint").hasAuthority("USER")
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}
@Bean
fun web(http: HttpSecurity): SecurityFilterChain {
    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 支持多种模式和多种规则;你也可以以编程方式创建自己的模式和规则。

一旦授权,你可以使用Spring 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>

一旦授权,你可以使用Spring 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来遵循此规则,如下所示

使用 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 权限,你可以这样做

按 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 权限;否则,拒绝请求”

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

一旦授权,你可以使用Spring 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 配置以允许 FORWARDERROR 等转发类型,如下所示

按转发类型匹配
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
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 路径和 controller 路径,如下所示

使用 MvcRequestMatcher 匹配
  • Java

  • Kotlin

  • Xml

@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();
}
@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)
        }
    }
<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,如下所示

按转发类型授权
  • Java

  • Kotlin

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

一旦授权,你可以使用Spring 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

  • 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 Taglibs。因此,本节将重点介绍这些领域的示例。

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

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

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

这提供的第一件事是为你的 SpEL 表达式提供了一组增强的授权字段和方法。以下是常用方法的快速概览

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

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

  • hasAuthority - 请求要求 Authentication 具有与给定值匹配的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 可能看起来像这样

Open Policy Agent 授权管理器
  • 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 允许它们,如下所示

允许静态资源
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

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

过去,这会带来性能折衷,因为 Spring Security 在每个请求上都会查询会话。然而,自 Spring Security 6 起,除非授权规则要求,否则不再查询会话。由于性能影响现已解决,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("/api/user/**").hasRole("USER")   (2)
				.requestMatchers("/api/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("/api/user/**", hasRole("USER"))                       (2)
                authorize("/api/admin/**", hasRole("ADMIN"))                     (3)
                authorize(anyRequest, authenticated)                             (4)
            }
        }
        return http.build()
    }

}
1 配置 HttpSecurity 仅应用于以 /api/ 开头的 URL
2 允许具有 USER 角色的用户访问以 /api/user/ 开头的 URL
3 允许具有 ADMIN 角色的用户访问以 /api/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("/api/user/**")).hasRole("USER")     (3)
				.requestMatchers(regexMatcher("/api/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("/api/user/**"), hasRole("USER"))           (3)
                authorize(regexMatcher("/api/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 角色的用户访问以 /api/user/ 开头的 URL,使用 AntPathRequestMatcher
4 允许具有 ADMIN 角色的用户访问以 /api/admin/ 开头的 URL,使用 RegexRequestMatcher
5 允许具有 SUPERVISOR 角色的用户访问与 MyCustomRequestMatcher 匹配的 URL,使用自定义的 RequestMatcher

延伸阅读

既然您已经保护了应用程序的请求,请考虑保护其方法。您还可以进一步阅读关于测试应用程序或将 Spring Security 与应用程序的其他方面集成的信息,例如数据层跟踪和度量