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 allows you to model your authorization at the request level.
For example, with Spring Security you can say that all pages under /admin
require one authority while all other pages simply require authentication.
默认情况下,Spring Security 要求对每个请求进行身份验证。也就是说,任何时候使用 “@36”,都有必要声明您的授权规则。
By default, Spring Security requires that every request be authenticated.
That said, any time you use an HttpSecurity
instance, it’s necessary to declare your authorization rules.
每当您有 HttpSecurity
实例时,您至少应该执行以下操作:
Whenever you have an HttpSecurity
instance, you should at least do:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/**" access="authenticated"/>
</http>
这告诉弹簧安全性,您应用程序中的任何端点至少需要安全上下文经过认证才能允许。
This tells Spring Security that any endpoint in your application requires that the security context at a minimum be authenticated in order to allow it.
在很多情况下,您的授权规则会比这更加复杂,因此请考虑以下用例:
In many cases, your authorization rules will be more sophisticated than that, so please consider the following use cases:
-
I have an app that uses
authorizeRequests
and I want to migrate-authorize-requests -
I want to request-authorization-architecture
-
I want to match-requests based on a pattern; specifically match-by-regex
-
I want to match request, and I map Spring MVC to mvc-not-default-servlet
-
I want to authorize-requests
-
I want to match-by-custom
-
I want to authorize-requests
-
I want to remote-authorization-manager to a policy agent
Understanding How Request Authorization Components Work
本节以 Servlet Architecture and Implementation 为基础,深入探究 authorization 在基于 Servlet 的应用程序中的请求级别工作方式。 |
This section builds on Servlet Architecture and Implementation by digging deeper into how authorization works at the request level in Servlet-based applications. |
-
First, the
AuthorizationFilter
constructs aSupplier
that retrieves an Authentication from the SecurityContextHolder. -
Second, it passes the
Supplier<Authentication>
and theHttpServletRequest
to theAuthorizationManager
. TheAuthorizationManager
matches the request to the patterns inauthorizeHttpRequests
, and runs the corresponding rule.-
If authorization is denied, an
AuthorizationDeniedEvent
is published, and anAccessDeniedException
is thrown. In this case theExceptionTranslationFilter
handles theAccessDeniedException
. -
If access is granted, an
AuthorizationGrantedEvent
is published andAuthorizationFilter
continues with the FilterChain which allows the application to process normally.
-
AuthorizationFilter
Is Last By Default
“@37” 默认在 “@39” 中排在最后。这意味着 Spring Security 的 “@40”、 “@41” 和其他过滤器集成不需要授权。如果您在 “@38” 之前添加自己的过滤器,它们也不需要授权;否则,它们会需要授权。
The AuthorizationFilter
is last in the Spring Security filter chain by default.
This means that Spring Security’s authentication filters, exploit protections, and other filter integrations do not require authorization.
If you add filters of your own before the AuthorizationFilter
, they will also not require authorization; otherwise, they will.
这通常变得重要的一个地方是当您添加 {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。
A place where this typically becomes important is when you are adding {spring-framework-reference-url}web.html#spring-web[Spring MVC] endpoints.
Because they are executed by the {spring-framework-reference-url}web.html#mvc-servlet[DispatcherServlet
] and this comes after the AuthorizationFilter
, your endpoints need to be authorizing-endpoints.
All Dispatches Are Authorized
AuthorizationFilter
不仅在每个请求上运行,还会在每次分派上运行。这意味着 REQUEST
分派需要授权,FORWARD
、ERROR
和 INCLUDE
也需要授权。
The AuthorizationFilter
runs not just on every request, but on every dispatch.
This means that the REQUEST
dispatch needs authorization, but also `FORWARD`s, `ERROR`s, and `INCLUDE`s.
例如,{spring-framework-reference-url}web.html#spring-web[Spring MVC] 可以 `FORWARD`将请求发送到呈现 Thymeleaf 模板的视图解析器,如下所示:
For example, {spring-framework-reference-url}web.html#spring-web[Spring MVC] can FORWARD
the request to a view resolver that renders a Thymeleaf template, like so:
-
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 以渲染“端点”模板。
In this case, authorization happens twice; once for authorizing /endpoint
and once for forwarding to Thymeleaf to render the "endpoint" template.
出于这个原因,您可能希望 permit all FORWARD
dispatches。
For that reason, you may want to match-by-dispatcher-type.
此原则的另一个示例是 Spring Boot 处理错误的方式。如果容器捕获到一个异常,比如以下内容:
Another example of this principle is how Spring Boot handles errors. If the container catches an exception, say like the following:
-
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
分派。
then Boot will dispatch it to the ERROR
dispatch.
在这种情况下,授权也会发生两次;一次是授权 /endpoint
,另一次是分派错误。
In that case, authorization also happens twice; once for authorizing /endpoint
and once for dispatching the error.
因此,你可能想要 permit all ERROR
dispatches。
For that reason, you may want to match-by-dispatcher-type.
Authentication
Lookup is Deferred
记住 “@44”。
Remember that the AuthorizationManager
API uses a Supplier<Authentication>
.
当请求 “@47” 时,这很重要。在这些情况下,不会查询 “@48”,从而使请求更快。
This matters with authorizeHttpRequests
when requests are authorize-requests.
In those cases, the Authentication
is not queried, making for a faster request.
Authorizing an Endpoint
你可以通过按照优先级顺序添加更多规则来配置 Spring Security,让其具有不同的规则。
You can configure Spring Security to have different rules by adding more rules in order of precedence.
如果您希望只允许拥有 USER
权限的最终用户访问 /endpoint
,您可以执行以下操作:
If you want to require that /endpoint
only be accessible by end users with the USER
authority, then you can do:
-
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>
如您所见,声明可以分解为模式/规则对。
As you can see, the declaration can be broken up in to pattern/rule pairs.
AuthorizationFilter
以所列顺序处理这些对,仅对请求应用第一个匹配项。这意味着,即使 /**
也将对 /endpoint
进行匹配,上面的规则也没有问题。阅读上述规则的方式为“如果请求是 /endpoint
,则需要 USER
权限;否则,仅需要身份验证”。
AuthorizationFilter
processes these pairs in the order listed, applying only the first match to the request.
This means that even though /**
would also match for /endpoint
the above rules are not a problem.
The way to read the above rules is "if the request is /endpoint
, then require the USER
authority; else, only require authentication".
Spring Security 支持多种模式和多种规则;您还可以以编程方式创建每种模式或规则。
Spring Security supports several patterns and several rules; you can also programmatically create your own of each.
获得授权后,您可以使用 “@49” 以以下方式对其进行测试:
Once authorized, you can test it using Security’s test support in the following way:
-
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。
Above you’ve already seen authorizing-endpoints.
您看到的第一个是最简单的,即匹配任何请求。
The first you saw was the simplest, which is to match any request.
第二个是按 URI 模式匹配。Spring Security 支持两种 URI 模式匹配语言:Ant(如上所示)和 Regular Expressions。
The second is to match by a URI pattern. Spring Security supports two languages for URI pattern-matching: match-by-ant (as seen above) and match-by-regex.
Matching Using Ant
Ant 是 Spring Security 用于匹配请求的默认语言。
Ant is the default language that Spring Security uses to match requests.
您可以使用它来匹配单个端点或目录,甚至可以捕获占位符以供以后使用。您还可以对其进行优化以匹配一组特定的 HTTP 方法。
You can use it to match a single endpoint or a directory, and you can even capture placeholders for later use. You can also refine it to match a specific set of HTTP methods.
假设您不是想匹配 /endpoint
端点,而是想匹配 /resource
目录下的所有端点。在这种情况下,您可以执行以下操作:
Let’s say that you instead of wanting to match the /endpoint
endpoint, you want to match all endpoints under the /resource
directory.
In that case, you can do something like the following:
-
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
权限;否则,仅需要身份验证”
The way to read this is "if the request is /resource
or some subdirectory, require the USER
authority; otherwise, only require authentication"
您还可以从请求中提取路径值,如下所示:
You can also extract path values from the request, as seen below:
-
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” 以以下方式对其进行测试:
Once authorized, you can test it using Security’s test support in the following way:
-
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 only matches paths. If you want to match query parameters, you will need a custom request matcher. |
Matching Using Regular Expressions
Spring Security 支持针对正则表达式匹配请求。如果您想对子目录应用比 **
更严格的匹配标准,这可能会派上用场。
Spring Security supports matching requests against a regular expression.
This can come in handy if you want to apply more strict matching criteria than **
on a subdirectory.
例如,考虑一条包含用户名且规则是所有用户名必须为字母数字的路径。您可以使用 {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[RegexRequestMatcher
] 遵守此规则,如下所示:
For example, consider a path that contains the username and the rule that all usernames must be alphanumeric.
You can use {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[RegexRequestMatcher
] to respect this rule, like so:
-
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 方法匹配规则。当根据授予的权限授权(如被授予 read
或 write
权限)时,此方法非常有用。
You can also match rules by HTTP method.
One place where this is handy is when authorizing by permissions granted, like being granted a read
or write
privilege.
要要求所有 GET`s to have the `read
权限和所有 POST`s to have the `write
权限,您可以执行以下操作:
To require all GET`s to have the `read
permission and all POST`s to have the `write
permission, you can do something like this:
-
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
权限;否则,拒绝请求”
These authorization rules should read as: "if the request is a GET, then require read
permission; else, if the request is a POST, then require write
permission; else, deny the request"
默认情况下拒绝请求是一种良好的安全实践,因为它将规则集变成允许列表。 |
Denying the request by default is a healthy security practice since it turns the set of rules into an allow list. |
获得授权后,您可以使用 “@49” 以以下方式对其进行测试:
Once authorized, you can test it using Security’s test support in the following way:
-
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 中不受支持 |
This feature is not currently supported in XML |
如前所述,Spring Security “@52”。尽管在 “@50” 调度上建立的 “@53” 延续到后续调度,但有时细微的不匹配会导致意外的 “@51”。
As stated earlier, Spring Security _all_dispatches_are_authorized.
And even though the security context established on the REQUEST
dispatch carries over to subsequent dispatches, subtle mismatches can sometimes cause an unexpected AccessDeniedException
.
为了解决此问题,您可以配置 Spring Security Java 配置以允许 FORWARD
和 ERROR
等调度类型,如下所示:
To address that, you can configure Spring Security Java configuration to allow dispatcher types like FORWARD
and ERROR
, like so:
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)
}
}
Using an MvcRequestMatcher
一般而言,你可以像上面展示的那样使用`requestMatchers(String)`。
Generally speaking, you can use requestMatchers(String)
as demonstrated above.
然而,如果你将 Spring MVC 映射到一个不同的 servlet 路径,那么你需要在安全配置中考虑这一点。
However, if you map Spring MVC to a different servlet path, then you need to account for that in your security configuration.
例如,如果 Spring MVC 映射到 /spring-mvc
而不是 /
(默认),那么你可能有一个端点,比如 /spring-mvc/my/controller
,你想授权它。
For example, if Spring MVC is mapped to /spring-mvc
instead of /
(the default), then you may have an endpoint like /spring-mvc/my/controller
that you want to authorize.
你需要使用 MvcRequestMatcher
来分割你的配置中的 servlet 路径和控制器路径,就像这样:
You need to use MvcRequestMatcher
to split the servlet path and the controller path in your configuration like so:
@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>
这一需求至少可以通过两种不同的方式产生:
This need can arise in at least two different ways:
-
If you use the
spring.mvc.servlet.path
Boot property to change the default path (/
) to something else -
If you register more than one Spring MVC
DispatcherServlet
(thus requiring that one of them not be the default path)
Using a Custom Matcher
该特性当前在 XML 中不受支持 |
This feature is not currently supported in XML |
在 Java 配置中,您可以创建自己的 {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[RequestMatcher
],并像这样供应 DSL:
In Java configuration, you can create your own {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[RequestMatcher
] and supply it to the DSL like so:
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)
}
}
由于 {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[ |
Because {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[ |
获得授权后,您可以使用 “@49” 以以下方式对其进行测试:
Once authorized, you can test it using Security’s test support in the following way:
-
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 seen、permitAll
、denyAll
和 hasAuthority
。
Once a request is matched, you can authorize it in several ways match-requests like permitAll
, denyAll
, and hasAuthority
.
简要总结一下,以下是内置于 DSL 的授权规则:
As a quick summary, here are the authorization rules built into the DSL:
-
permitAll
- The request requires no authorization and is a public endpoint; note that in this case, theAuthentication
is never retrieved from the session -
denyAll
- The request is not allowed under any circumstances; note that in this case, theAuthentication
is never retrieved from the session -
hasAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches the given value -
hasRole
- A shortcut forhasAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasAnyAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches any of the given values -
hasAnyRole
- A shortcut forhasAnyAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
access
- The request uses this customAuthorizationManager
to determine access
现在已经学习了模式、规则以及它们如何组合在一起,你应该能够理解此更复杂的示例中发生的事情:
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
-
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 | There are multiple authorization rules specified. Each rule is considered in the order they were declared. |
2 | Dispatches FORWARD and ERROR are permitted to allow {spring-framework-reference-url}web.html#spring-web[Spring MVC] to render views and Spring Boot to render errors |
3 | We specified multiple URL patterns that any user can access. Specifically, any user can access a request if the URL starts with "/static/", equals "/signup", or equals "/about". |
4 | Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the hasRole method we do not need to specify the "ROLE_" prefix. |
5 | Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix. |
6 | Any URL that has not already been matched on is denied access. This is a good strategy if you do not want to accidentally forget to update your authorization rules. |
Expressing Authorization with SpEL
尽管建议使用具体的 AuthorizationManager
,但有时需要表达式,例如使用 <intercept-url>
或 JSP Taglibs。因此,本节将重点介绍来自这些领域的示例。
While using a concrete AuthorizationManager
is recommended, there are some cases where an expression is necessary, like with <intercept-url>
or with JSP Taglibs.
For that reason, this section will focus on examples from those domains.
考虑到这一点,我们来更深入地了解一下 Spring Security 的 Web Security Authorization SpEL API。
Given that, let’s cover Spring Security’s Web Security Authorization SpEL API a bit more in depth.
Spring Security 将其所有授权字段和方法封装在一个根对象集中。最通用的根对象称为 SecurityExpressionRoot
,它构成了 WebSecurityExpressionRoot
的基础。在准备评估授权表达式时,Spring Security 会将该根对象提供给 StandardEvaluationContext
。
Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
The most generic root object is called SecurityExpressionRoot
and it forms the basis for WebSecurityExpressionRoot
.
Spring Security supplies this root object to StandardEvaluationContext
when preparing to evaluate an authorization expression.
Using Authorization Expression Fields and Methods
此提供的第一个内容是为 SpEL 表达式提供了一组增强型授权字段和方法。以下是常用方法的快速概览:
The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions. What follows is a quick overview of the most common methods:
-
permitAll
- The request requires no authorization to be invoked; note that in this case, theAuthentication
is never retrieved from the session -
denyAll
- The request is not allowed under any circumstances; note that in this case, theAuthentication
is never retrieved from the session -
hasAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches the given value -
hasRole
- A shortcut forhasAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasAnyAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches any of the given values -
hasAnyRole
- A shortcut forhasAnyAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasPermission
- A hook into yourPermissionEvaluator
instance for doing object-level authorization
以下是常用字段的简要介绍:
And here is a brief look at the most common fields:
-
authentication
- TheAuthentication
instance associated with this method invocation -
principal
- TheAuthentication#getPrincipal
associated with this method invocation
现在已经学习了模式、规则以及它们如何组合在一起,你应该能够理解此更复杂的示例中发生的事情:
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
-
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 | We specified a URL pattern that any user can access. Specifically, any user can access a request if the URL starts with "/static/". |
2 | Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the hasRole method we do not need to specify the "ROLE_" prefix. |
3 | Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix. |
4 | Any URL that has not already been matched on is denied access. This is a good strategy if you do not want to accidentally forget to update your authorization rules. |
Using Path Parameters
此外,Spring Security 提供了一种发现路径参数的机制,以便它们也可以在 SpEL 表达式中访问。
Additionally, Spring Security provides a mechanism for discovering path parameters so they can also be accessed in the SpEL expression as well.
例如,你可以在你的 SpEL 表达式中以下面的方式访问一个路径参数:
For example, you can access a path parameter in your SpEL expression in the following way:
-
Xml
<http>
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
此表达式引用 /resource/
之后的路径变量,并要求它等于 Authentication#getName
。
This expression refers to the path variable after /resource/
and requires that it is equal to Authentication#getName
.
Use an Authorization Database, Policy Agent, or Other Service
如果你想配置 Spring Security 以便为授权使用一个单独的服务,你可以创建自己的 AuthorizationManager
并将其与 anyRequest
匹配。
If you want to configure Spring Security to use a separate service for authorization, you can create your own AuthorizationManager
and match it to anyRequest
.
首先,你的 AuthorizationManager
可能看起来像这样:
First, your AuthorizationManager
may look something like this:
-
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:
Then, you can wire it into Spring Security in the following way:
-
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
允许它们,就像这样:
When you have static resources it can be tempting to configure the filter chain to ignore these values.
A more secure approach is to permit them using permitAll
like so:
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/css/**").permitAll()
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/css/**", permitAll)
authorize(anyRequest, authenticated)
}
}
它更安全,因为即使对于静态资源,编写安全标头也很重要,如果忽略请求,Spring Security 无法做到这一点。
It’s more secure because even with static resources it’s important to write secure headers, which Spring Security cannot do if the request is ignored.
过去,由于 Spring Security 在每个请求上都咨询会话,因此这带来了性能权衡。然而,在 Spring Security 6 中,除非授权规则要求,否则不再对会话进行 ping。由于性能影响现已得到解决,因此 Spring Security 建议至少对所有请求使用 permitAll
。
In this past, this came with a performance tradeoff since the session was consulted by Spring Security on every request.
As of Spring Security 6, however, the session is no longer pinged unless required by the authorization rule.
Because the performance impact is now addressed, Spring Security recommends using at least permitAll
for all requests.
Migrating from authorizeRequests
|
|
{security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[@54] 提供 “HttpServletRequest” 的 “@55”。它被插入到 “@56” 中,作为 “@57” 之一。
The {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[AuthorizationFilter
] provides authorization for `HttpServletRequest`s.
It is inserted into the FilterChainProxy as one of the Security Filters.
在声明 SecurityFilterChain
时,您可以覆盖默认设置。不要使用 {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[authorizeRequests
],使用 authorizeHttpRequests
,就像这样:
You can override the default when you declare a SecurityFilterChain
.
Instead of using {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[authorizeRequests
], use authorizeHttpRequests
, like so:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated();
)
// ...
return http.build();
}
这以多种方式改进了 authorizeRequests
:
This improves on authorizeRequests
in a number of ways:
-
Uses the simplified
AuthorizationManager
API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization. -
Delays
Authentication
lookup. Instead of the authentication needing to be looked up for every request, it will only look it up in requests where an authorization decision requires authentication. -
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
].
When authorizeHttpRequests
is used instead of authorizeRequests
, then {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[AuthorizationFilter
] is used instead of {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。
Where possible, it is recommended that you use type-safe authorization managers instead of SpEL.
For Java configuration, {security-api-url}org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.html[WebExpressionAuthorizationManager
] is available to help migrate legacy SpEL.
要使用 WebExpressionAuthorizationManager
,你可以通过要迁移的表达式构造一个,如下所示:
To use WebExpressionAuthorizationManager
, you can construct one with the expression you are trying to migrate, like so:
-
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,这看起来如下所示:
If you are referring to a bean in your expression like so: @webSecurity.check(authentication, request)
, it’s recommended that you instead call the bean directly, which will look something like the following:
-
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)
引用它们。
For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement AuthorizationManager
and refer to them by calling .access(AuthorizationManager)
.
如果您无法做到这一点,您可以使用 bean 解析器配置 {security-api-url}org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.html[DefaultHttpSecurityExpressionHandler
],然后将其提供给 WebExpressionAuthorizationManager#setExpressionhandler
.
If you are not able to do that, you can configure a {security-api-url}org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.html[DefaultHttpSecurityExpressionHandler
] with a bean resolver and supply that to WebExpressionAuthorizationManager#setExpressionhandler
.
Security Matchers
{security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[@58] 接口用于确定请求是否匹配给定规则。我们使用 “@59” 来确定是否应将 “@62” 应用到给定请求。同样,我们可以使用 “@61” 来确定我们应将哪些授权规则应用到给定请求。请看以下示例:
The {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[RequestMatcher
] interface is used to determine if a request matches a given rule.
We use securityMatchers
to determine if a given HttpSecurity
should be applied to a given request.
The same way, we can use requestMatchers
to determine the authorization rules that we should apply to a given request.
Look at the following example:
-
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 | Configure HttpSecurity to only be applied to URLs that start with /api/ |
2 | Allow access to URLs that start with /user/ to users with the USER role |
3 | Allow access to URLs that start with /admin/ to users with the ADMIN role |
4 | Any other request that doesn’t match the rules above, will require authentication |
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.
The securityMatcher(s)
and requestMatcher(s)
methods will decide which RequestMatcher
implementation fits best for your application: If {spring-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then {security-api-url}org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.html[MvcRequestMatcher
] will be used, otherwise, {security-api-url}org/springframework/security/web/servlet/util/matcher/AntPathRequestMatcher.html[AntPathRequestMatcher
] will be used.
You can read more about the Spring MVC integration here.
如果你想使用特定的 RequestMatcher
,只需将实现传递给 securityMatcher
和/或 requestMatcher
方法:
If you want to use a specific RequestMatcher
, just pass an implementation to the securityMatcher
and/or requestMatcher
methods:
-
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 | Import the static factory methods from AntPathRequestMatcher and RegexRequestMatcher to create RequestMatcher instances. |
2 | Configure HttpSecurity to only be applied to URLs that start with /api/ , using AntPathRequestMatcher |
3 | Allow access to URLs that start with /user/ to users with the USER role, using AntPathRequestMatcher |
4 | Allow access to URLs that start with /admin/ to users with the ADMIN role, using RegexRequestMatcher |
5 | Allow access to URLs that match the MyCustomRequestMatcher to users with the SUPERVISOR role, using a custom RequestMatcher |
Further Reading
既然您已经保护了应用程序的请求,请考虑 securing its methods.您还可以进一步阅读有关 testing your application 或将 Spring Security 与应用程序的其他方面(如 the data layer 或 tracing and metrics)集成的内容。
Now that you have secured your application’s requests, consider securing its methods. You can also read further on testing your application or on integrating Spring Security with other aspects of you application like the data layer or tracing and metrics.