授权 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 的应用程序中授权在请求级别是如何工作的。
图 1. 授权 HttpServletRequest
-
1 首先,
AuthorizationFilter
构造一个Supplier
,该Supplier
从 SecurityContextHolder 中检索 Authentication。 -
2 其次,它将
Supplier<Authentication>
和HttpServletRequest
传递给 AuthorizationManager。AuthorizationManager
将请求与authorizeHttpRequests
中的模式进行匹配,并运行相应的规则。-
3 如果授权被拒绝,则 发布一个 AuthorizationDeniedEvent,并抛出一个
AccessDeniedException
。在这种情况下,ExceptionTranslationFilter 处理AccessDeniedException
。 -
4 如果访问被授予,则 发布一个 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
时很重要,当请求总是被允许或总是被拒绝时。在这些情况下,认证不会被查询,从而使请求更快。
授权端点
你可以通过按优先级顺序添加更多规则来配置 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 支持多种模式和规则;您也可以以编程方式创建自己的模式和规则。
一旦授权,您可以使用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
这样的调度器类型,如下所示:
- 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 路径和控制器路径,如下所示:
- 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
方法。
一旦授权,你可以通过以下方式使用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
- 请求不需要授权,是一个公开的端点;请注意,在这种情况下,认证 从未从会话中检索 -
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();
}
指定了多个授权规则。每个规则按照声明的顺序进行考虑。
允许
FORWARD
和ERROR
调度,以便 Spring MVC 渲染视图和 Spring Boot 渲染错误。我们指定了多个任何用户都可以访问的 URL 模式。具体来说,如果 URL 以 "/static/" 开头、等于 "/signup" 或等于 "/about",则任何用户都可以访问该请求。
任何以 "/admin/" 开头的 URL 将限制为具有 "ROLE_ADMIN" 角色的用户。请注意,由于我们调用了
hasRole
方法,因此不需要指定 "ROLE_" 前缀。任何以 "/db/" 开头的 URL 需要用户同时被授予 "db" 权限并具有 "ROLE_ADMIN" 角色。请注意,由于我们使用了
hasRole
表达式,因此不需要指定 "ROLE_" 前缀。任何尚未匹配的 URL 都会被拒绝访问。如果您不想意外忘记更新您的授权规则,这是一个好策略。
使用SpEL表达授权
虽然建议使用具体的 AuthorizationManager
,但在某些情况下,比如使用 <intercept-url>
或 JSP 标签库时,表达式是必需的。因此,本节将重点介绍这些领域的示例。
鉴于此,让我们更深入地了解一下 Spring Security 的 Web 安全授权 SpEL API。
Spring Security 将其所有的授权字段和方法封装在一组根对象中。最通用的根对象称为 SecurityExpressionRoot
,它是 WebSecurityExpressionRoot
的基础。当准备评估授权表达式时,Spring Security 会将此根对象提供给 StandardEvaluationContext
。
使用授权表达式字段和方法
这首先为您的SpEL表达式提供了一组增强的授权字段和方法。以下是最常用方法的快速概述:
-
permitAll
- 请求不需要任何授权即可调用;请注意,在这种情况下,认证 从未从会话中检索 -
denyAll
- 在任何情况下都不允许请求;请注意,在这种情况下,Authentication
从未从会话中检索 -
hasAuthority
- 请求要求Authentication
具有与给定值匹配的 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>
我们指定了一个任何用户都可以访问的 URL 模式。具体来说,如果 URL 以 "/static/" 开头,则任何用户都可以访问该请求。
任何以 "/admin/" 开头的 URL 都将限制为具有 "ROLE_ADMIN" 角色的用户。你会注意到,由于我们调用了
hasRole
方法,因此不需要指定 "ROLE_" 前缀。任何以 "/db/" 开头的 URL 都要求用户同时被授予 "db" 权限以及是 "ROLE_ADMIN"。你会注意到,由于我们使用了
hasRole
表达式,因此不需要指定 "ROLE_" 前缀。任何尚未匹配的 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
允许它们,如下所示:
- 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 开始,除非授权规则需要,否则不会再 ping 会话。由于性能影响现在已得到解决,Spring Security 建议对所有请求至少使用 permitAll
。
从 authorizeRequests
迁移
AuthorizationFilter
取代了 FilterSecurityInterceptor。为了保持向后兼容,FilterSecurityInterceptor
仍然是默认配置。本节讨论 AuthorizationFilter
的工作原理以及如何覆盖默认配置。
AuthorizationFilter 为 HttpServletRequest
提供授权。它被插入到 FilterChainProxy 中,作为 Security Filters 之一。
你可以覆盖默认设置,在声明 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)
来引用它们。
如果你无法做到这一点,你可以配置一个 DefaultHttpSecurityExpressionHandler 并使用 bean 解析器,然后将其提供给 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()
}
}
配置
HttpSecurity
仅应用于以/api/
开头的 URL允许具有
USER
角色的用户访问以/user/
开头的 URL允许具有
ADMIN
角色的用户访问以/admin/
开头的 URL任何其他不匹配上述规则的请求,都将需要身份验证
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()
}
}
从
AntPathRequestMatcher
和RegexRequestMatcher
导入静态工厂方法以创建RequestMatcher
实例。配置
HttpSecurity
仅应用于以/api/
开头的 URL,使用AntPathRequestMatcher
。允许具有
USER
角色的用户访问以/user/
开头的 URL,使用AntPathRequestMatcher
。允许具有
ADMIN
角色的用户访问以/admin/
开头的 URL,使用RegexRequestMatcher
。允许具有
SUPERVISOR
角色的用户访问与MyCustomRequestMatcher
匹配的 URL,使用自定义的RequestMatcher
。