授权 HttpServletRequests
Spring Security 允许您在请求级别对授权进行建模。例如,使用 Spring Security,您可以指定 /admin
下的所有页面都需要一个权限,而所有其他页面只需要身份验证。
默认情况下,Spring Security 要求每个请求都经过身份验证。也就是说,无论何时使用HttpSecurity
实例,都需要声明授权规则。
无论何时拥有 HttpSecurity
实例,至少应该执行以下操作
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/**" access="authenticated"/>
</http>
这告诉 Spring Security,应用程序中的任何端点都需要安全上下文至少经过身份验证才能允许访问。
在许多情况下,授权规则将比这更复杂,因此请考虑以下用例
-
我有一个使用
authorizeRequests
的应用程序,我想迁移到authorizeHttpRequests
-
我想匹配请求,并将 Spring MVC 映射到非默认 servlet
-
我想授权请求
了解请求授权组件的工作原理
本节基于Servlet 架构和实现,更深入地探讨了在基于 Servlet 的应用程序中授权如何在请求级别工作。 |
-
首先,
AuthorizationFilter
构造一个Supplier
,它从身份验证中检索SecurityContextHolder。 -
其次,它将
Supplier<Authentication>
和HttpServletRequest
传递给AuthorizationManager
。AuthorizationManager
将请求与authorizeHttpRequests
中的模式匹配,并运行相应的规则。-
如果授权被拒绝,将发布
AuthorizationDeniedEvent
,并抛出AccessDeniedException
。在这种情况下,ExceptionTranslationFilter
将处理AccessDeniedException
。 -
如果授予访问权限,将发布
AuthorizationGrantedEvent
,AuthorizationFilter
将继续使用FilterChain,这将允许应用程序正常处理。
-
AuthorizationFilter
默认情况下是最后一个
AuthorizationFilter
默认情况下是Spring Security 过滤器链中的最后一个。这意味着 Spring Security 的身份验证过滤器、漏洞利用保护和其他过滤器集成不需要授权。如果您在 AuthorizationFilter
之前添加了自己的过滤器,它们也不需要授权;否则,它们将需要授权。
这通常在您添加Spring MVC 端点时变得很重要。因为它们是由DispatcherServlet
执行的,并且它位于 AuthorizationFilter
之后,所以您的端点需要包含在 authorizeHttpRequests
中才能被允许。
所有调度都已授权
AuthorizationFilter
不仅在每个请求上运行,而且在每个调度上运行。这意味着 REQUEST
调度需要授权,但 FORWARD
、ERROR
和 INCLUDE
也需要授权。
例如,Spring MVC 可以将请求 FORWARD
到一个视图解析器,该解析器渲染 Thymeleaf 模板,如下所示
-
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 如何处理错误。如果容器捕获到异常,例如以下情况
-
Java
-
Kotlin
@Controller
public class MyController {
@GetMapping("/endpoint")
public String endpoint() {
throw new UnsupportedOperationException("unsupported");
}
}
@Controller
class MyController {
@GetMapping("/endpoint")
fun endpoint(): String {
throw UnsupportedOperationException("unsupported")
}
}
那么 Boot 将将其调度到 ERROR
调度。
在这种情况下,授权也会发生两次;一次用于授权 /endpoint
,一次用于调度错误。
因此,您可能希望允许所有 ERROR
调度。
Authentication
查找被延迟
当请求始终被允许或始终被拒绝时,这在 authorizeHttpRequests
中很重要。在这些情况下,Authentication
不会被查询,从而使请求更快。
授权端点
您可以通过按优先级顺序添加更多规则来配置 Spring Security 以具有不同的规则。
如果您希望 /endpoint
只能被具有 USER
权限的最终用户访问,那么您可以执行以下操作
-
Java
-
Kotlin
-
Xml
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").hasAuthority("USER")
.anyRequest().authenticated()
)
// ...
return http.build();
}
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http {
authorizeHttpRequests {
authorize("/endpoint", hasAuthority("USER"))
authorize(anyRequest, authenticated)
}
}
return http.build();
}
<http>
<intercept-url pattern="/endpoint" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
如您所见,声明可以分解为模式/规则对。
AuthorizationFilter
按列出的顺序处理这些对,只将第一个匹配项应用于请求。这意味着即使 /**
也匹配 /endpoint
,上面的规则也不成问题。阅读上面规则的方法是“如果请求是 /endpoint
,则需要 USER
权限;否则,只需要身份验证”。
Spring Security 支持多种模式和规则;您也可以以编程方式创建自己的模式和规则。
授权后,您可以使用 Security 的测试支持 以以下方式进行测试
-
Java
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
匹配请求
您已经看到了 两种匹配请求的方法。
您看到的第一种方法是最简单的,即匹配任何请求。
使用 Ant 匹配
Ant 是 Spring Security 用于匹配请求的默认语言。
您可以使用它来匹配单个端点或目录,甚至可以捕获占位符以供日后使用。您还可以对其进行细化以匹配特定的一组 HTTP 方法。
假设您想要匹配 /endpoint
端点,而不是 /resource
目录下的所有端点。在这种情况下,您可以执行以下操作
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/**").hasAuthority("USER")
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/resource/**", hasAuthority("USER"))
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/resource/**" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
这样理解:如果请求是 /resource
或其子目录,则需要 USER
权限;否则,只需要身份验证。
您还可以从请求中提取路径值,如下所示
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
授权后,您可以使用 Security 的测试支持 以以下方式进行测试
-
Java
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
Spring Security 仅匹配路径。如果您想匹配查询参数,则需要自定义请求匹配器。 |
使用正则表达式匹配
Spring Security 支持将请求与正则表达式进行匹配。如果您想对子目录应用比 **
更严格的匹配条件,这将非常有用。
例如,考虑一个包含用户名且所有用户名必须是字母数字的路径。您可以使用 RegexRequestMatcher
来遵守此规则,如下所示
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
.anyRequest().denyAll()
)
http {
authorizeHttpRequests {
authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
authorize(anyRequest, denyAll)
}
}
<http>
<intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="denyAll"/>
</http>
按 HTTP 方法匹配
您也可以按 HTTP 方法匹配规则。这在通过授予的权限进行授权时非常有用,例如授予 `read` 或 `write` 权限。
要要求所有 `GET` 请求具有 `read` 权限,所有 `POST` 请求具有 `write` 权限,您可以执行以下操作
-
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 的测试支持 以以下方式进行测试
-
Java
@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
this.mvc.perform(get("/any"))
.andExpect(status().isForbidden());
}
@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
this.mvc.perform(post("/any").with(csrf()))
.andExpect(status().isOk());
}
@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
this.mvc.perform(get("/any").with(csrf()))
.andExpect(status().isForbidden());
}
按调度程序类型匹配
此功能目前在 XML 中不受支持 |
如前所述,Spring Security 默认情况下授权所有调度程序类型。即使 在 `REQUEST` 调度上建立的安全上下文 会延续到后续调度,但细微的不匹配有时会导致意外的 `AccessDeniedException`。
为了解决这个问题,您可以配置 Spring Security Java 配置以允许 `FORWARD` 和 `ERROR` 等调度程序类型,如下所示
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 路径和控制器路径分开,如下所示
@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,如下所示
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 方法。
|
授权后,您可以使用 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());
}
授权请求
一旦请求匹配,您可以通过多种方式对其进行授权 已经看到,例如 permitAll
、denyAll
和 hasAuthority
。
简而言之,以下是 DSL 中内置的授权规则
-
permitAll
- 请求不需要授权,是一个公共端点;请注意,在这种情况下,Authentication
从未从会话中检索 -
denyAll
- 请求在任何情况下都不允许;请注意,在这种情况下,Authentication
从未从会话中检索 -
hasAuthority
- 请求要求Authentication
具有 一个GrantedAuthority
,该GrantedAuthority
与给定值匹配 -
hasRole
-hasAuthority
的快捷方式,它以ROLE_
或配置为默认前缀的任何内容为前缀 -
hasAnyAuthority
- 请求要求Authentication
具有与任何给定值匹配的GrantedAuthority
-
hasAnyRole
-hasAnyAuthority
的快捷方式,它以ROLE_
或配置为默认前缀的任何内容为前缀 -
access
- 请求使用此自定义AuthorizationManager
来确定访问权限
现在您已经了解了模式、规则以及它们如何配对,您应该能够理解这个更复杂的示例中发生了什么
-
Java
import static jakarta.servlet.DispatcherType.*;
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(authorize -> authorize (1)
.dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
.requestMatchers("/static/**", "/signup", "/about").permitAll() (3)
.requestMatchers("/admin/**").hasRole("ADMIN") (4)
.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN"))) (5)
.anyRequest().denyAll() (6)
);
return http.build();
}
1 | 指定了多个授权规则。每个规则都按声明的顺序进行考虑。 |
2 | 调度 FORWARD 和 ERROR 被允许,以允许 Spring MVC 呈现视图,并允许 Spring Boot 呈现错误 |
3 | 我们指定了多个任何用户都可以访问的 URL 模式。具体来说,如果 URL 以 "/static/" 开头、等于 "/signup" 或等于 "/about",则任何用户都可以访问请求。 |
4 | 任何以“/admin/”开头的 URL 将被限制为具有“ROLE_ADMIN”角色的用户访问。您会注意到,由于我们调用了hasRole 方法,因此不需要指定“ROLE_”前缀。 |
5 | 任何以“/db/”开头的 URL 要求用户既被授予“db”权限,又是“ROLE_ADMIN”。您会注意到,由于我们使用的是hasRole 表达式,因此不需要指定“ROLE_”前缀。 |
6 | 任何尚未匹配的 URL 都被拒绝访问。如果您不想意外忘记更新授权规则,这是一个很好的策略。 |
使用 SpEL 表达授权
虽然建议使用具体的AuthorizationManager
,但在某些情况下,表达式是必要的,例如使用<intercept-url>
或 JSP 标签库。因此,本节将重点介绍来自这些领域的示例。
鉴于此,让我们更深入地了解 Spring Security 的 Web 安全授权 SpEL API。
Spring Security 将其所有授权字段和方法封装在一组根对象中。最通用的根对象称为SecurityExpressionRoot
,它是WebSecurityExpressionRoot
的基础。Spring Security 在准备评估授权表达式时,将此根对象提供给StandardEvaluationContext
。
使用授权表达式字段和方法
首先,它为您的 SpEL 表达式提供了一组增强的授权字段和方法。以下是最常用方法的简要概述
-
permitAll
- 请求不需要任何授权即可调用;请注意,在这种情况下,Authentication
永远不会从会话中检索 -
denyAll
- 请求在任何情况下都不允许;请注意,在这种情况下,Authentication
从未从会话中检索 -
hasAuthority
- 请求要求Authentication
具有 一个GrantedAuthority
,该GrantedAuthority
与给定值匹配 -
hasRole
-hasAuthority
的快捷方式,它以ROLE_
或配置为默认前缀的任何内容为前缀 -
hasAnyAuthority
- 请求要求Authentication
具有与任何给定值匹配的GrantedAuthority
-
hasAnyRole
-hasAnyAuthority
的快捷方式,它以ROLE_
或配置为默认前缀的任何内容为前缀 -
hasPermission
- 与您的PermissionEvaluator
实例挂钩,用于执行对象级授权
以下是几个最常用的字段
-
authentication
- 与此方法调用关联的Authentication
实例 -
principal
- 与此方法调用关联的Authentication#getPrincipal
现在您已经了解了模式、规则以及它们如何配对,您应该能够理解这个更复杂的示例中发生了什么
-
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 表达式中访问路径参数
-
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
允许它们,如下所示
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/css/**").permitAll()
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/css/**", permitAll)
authorize(anyRequest, authenticated)
}
}
它更安全,因为即使是静态资源,编写安全标头也很重要,而如果请求被忽略,Spring Security 无法做到这一点。
过去,这会带来性能权衡,因为 Spring Security 会在每个请求上查询会话。但是,从 Spring Security 6 开始,除非授权规则需要,否则不再 ping 会话。由于现在解决了性能影响,Spring Security 建议至少对所有请求使用 permitAll
。
从 authorizeRequests
迁移
AuthorizationFilter 取代了 FilterSecurityInterceptor 。为了保持向后兼容性,FilterSecurityInterceptor 仍然是默认值。本节讨论 AuthorizationFilter 的工作原理以及如何覆盖默认配置。
|
AuthorizationFilter
为 HttpServletRequest
提供 授权。它被插入到 FilterChainProxy 中,作为 安全过滤器 之一。
当您声明 SecurityFilterChain
时,您可以覆盖默认值。不要使用 authorizeRequests
,而是使用 authorizeHttpRequests
,如下所示
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated();
)
// ...
return http.build();
}
这在许多方面改进了 authorizeRequests
-
使用简化的
AuthorizationManager
API,而不是元数据源、配置属性、决策管理器和投票者。这简化了重用和定制。 -
延迟
Authentication
查找。它不会在每个请求中查找身份验证,而是在授权决策需要身份验证的请求中查找。 -
基于 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 | 从AntPathRequestMatcher 和RegexRequestMatcher 导入静态工厂方法以创建RequestMatcher 实例。 |
2 | 配置HttpSecurity ,使其仅应用于以/api/ 开头的 URL,使用AntPathRequestMatcher 。 |
3 | 允许具有USER 角色的用户访问以/user/ 开头的 URL,使用AntPathRequestMatcher 。 |
4 | 允许具有ADMIN 角色的用户访问以/admin/ 开头的 URL,使用RegexRequestMatcher 。 |
5 | 允许具有SUPERVISOR 角色的用户访问与MyCustomRequestMatcher 匹配的 URL,使用自定义的RequestMatcher 。 |