授权 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以具有不同的规则。

如果您希望仅具有USER权限的最终用户才能访问/endpoint,则可以执行以下操作:

授权端点
  • 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支持多种模式和规则;您也可以以编程方式创建自己的模式和规则。

授权后,您可以使用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方法。

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

使用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方法匹配规则。授权按授予的权限(例如授予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权限;否则,拒绝请求”。

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

授权后,您可以使用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等分发程序类型,如下所示:

示例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是一个函数式接口,所以您可以将其作为lambda提供给DSL。但是,如果您想从请求中提取值,则需要一个具体的类,因为这需要重写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

  • 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

  • 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 使用AntPathRequestMatcherHttpSecurity配置为仅应用于以/api/开头的 URL
3 使用AntPathRequestMatcher允许具有USER角色的用户访问以/user/开头的 URL
4 使用RegexRequestMatcher允许具有ADMIN角色的用户访问以/admin/开头的 URL
5 使用自定义RequestMatcher允许具有SUPERVISOR角色的用户访问与MyCustomRequestMatcher匹配的 URL

进一步阅读

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