阅读spring security文档
This commit is contained in:
@@ -51,6 +51,22 @@
|
|||||||
- [AuthorizationManagers](#authorizationmanagers)
|
- [AuthorizationManagers](#authorizationmanagers)
|
||||||
- [自定义AuthorizationManager](#自定义authorizationmanager)
|
- [自定义AuthorizationManager](#自定义authorizationmanager)
|
||||||
- [ROLE继承](#role继承)
|
- [ROLE继承](#role继承)
|
||||||
|
- [基于HttpServletRequest的权限认证](#基于httpservletrequest的权限认证)
|
||||||
|
- [基于Request的Authorization Component实现](#基于request的authorization-component实现)
|
||||||
|
- [AuthorizationFilter默认情况下是SecurityFilterChain中的最后一个filter](#authorizationfilter默认情况下是securityfilterchain中的最后一个filter)
|
||||||
|
- [Authentication查询是延迟的](#authentication查询是延迟的)
|
||||||
|
- [authorizing endpoint](#authorizing-endpoint)
|
||||||
|
- [请求匹配的pattern声明](#请求匹配的pattern声明)
|
||||||
|
- [Ant](#ant)
|
||||||
|
- [regex](#regex)
|
||||||
|
- [通过Http Method来进行匹配](#通过http-method来进行匹配)
|
||||||
|
- [自定义Matcher来进行匹配](#自定义matcher来进行匹配)
|
||||||
|
- [Authorizing Request](#authorizing-request)
|
||||||
|
- [通过Spel表达式来执行权限认证](#通过spel表达式来执行权限认证)
|
||||||
|
- [使用Authorization表达式](#使用authorization表达式)
|
||||||
|
- [使用path变量](#使用path变量)
|
||||||
|
- [使用数据库来进行权限认证](#使用数据库来进行权限认证)
|
||||||
|
- [SecurityMatcher](#securitymatcher)
|
||||||
|
|
||||||
|
|
||||||
# Spring Security
|
# Spring Security
|
||||||
@@ -911,18 +927,227 @@ static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHiera
|
|||||||
```
|
```
|
||||||
在上述示例中,具有`ADMIN => STAFF => USER => GUEST`的继承关系。一个拥有`ROLE_ADMIN`角色的用户将自动拥有其他角色的权限,其中`>`符号代表包含关系。
|
在上述示例中,具有`ADMIN => STAFF => USER => GUEST`的继承关系。一个拥有`ROLE_ADMIN`角色的用户将自动拥有其他角色的权限,其中`>`符号代表包含关系。
|
||||||
|
|
||||||
|
### 基于HttpServletRequest的权限认证
|
||||||
|
spring security允许基于request的级别构建认证模型。例如,通过spring security可以构建如下权限认证模型:所有`/admin`下的资源访问都需要特定的权限,而其他的资源访问则只需要身份认证通过即可。
|
||||||
|
|
||||||
|
默认情况下,spring security需要每个请求都必须通过身份认证。故而,在每次构建HttpSecurity实例时,必须都要声明`authorization rules`。
|
||||||
|
|
||||||
|
每次在声明HttpSecurity时,都至少需要指定如下`authorization rules`,除此之外还可以指定更加复杂的`auhorization rules`:
|
||||||
|
```java
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
上述配置会指定在访问任何endpoint时,至少都需要当前用户通过身份认证。
|
||||||
|
|
||||||
|
#### 基于Request的Authorization Component实现
|
||||||
|
1. 首先,AuthorizationFilter会构建一个`Supplier<Authenticaiton>`,该Supplier将会从SecurityContextHolder中获取Authentication独享
|
||||||
|
2. AuthorizationFilter将会把`Supplier<Authentication>`和`HttpServletRequest`传递给`AuthorizationManager`。`AuthorizationManager`将会在HttpSecurity的authorizeHttpRequests指定的authorization rules中匹配到的rule并执行。
|
||||||
|
1. 如果当前鉴权操作被拒绝,AuthorizationDeniedEvent事件将会被发布,并会抛出AccessDeniedException异常,在这种情况下,ExceptionTranslationFilter将会对异常进行处理
|
||||||
|
2. 如果当前鉴权校验通过,`AuthorzationGrantedEvent`事件将会被发布,`AuthorizationFilter`将会继续执行filterChain,允许程序正常的继续执行
|
||||||
|
|
||||||
|
#### AuthorizationFilter默认情况下是SecurityFilterChain中的最后一个filter
|
||||||
|
默认情况下,AuthorizationFilter是SecurityFilterChain中的最后一个filter。这意味着spring security中身份认证、漏洞保护和其他filter中的集成功能都不需要经过授权操作。***如果向AuthorizationFilter之前加入自定义filter,那么自定义filter也不需要经过授权校验;如果向AuthorizationFilter之后加入自定义filter,那么在执行自定义filter逻辑之前,需要经过授权校验。***
|
||||||
|
|
||||||
|
> 在通过spring mvc添加自定义的endpoint时,需要明确controller method是在AuthorizationFilter之后才执行的,在执行endpoint的逻辑之前,需要保证该`authorizeHttpRequests`中定义的rules中,对endpoint的访问应该被授权。
|
||||||
|
|
||||||
|
#### Authentication查询是延迟的
|
||||||
|
`AuthorizationManager` api使用了`Supplier<Authentication>`,故而在`authorizeHttpRequests`时,如果所有请求都被允许或所有请求都被拒绝的情况下,并不会去查询Authentication,这将会令请求处理得更快。
|
||||||
|
|
||||||
|
#### authorizing endpoint
|
||||||
|
当为spring security按顺序配置了多个rules时,如果想要/endpoint只能够被拥有`USER`权限的用户访问,可以通过如下配置来指定:
|
||||||
|
```java
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers("/endpoint").hasAuthority("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
如上所示,对rules的配置可以拆分成多个`pattern/rule`集合。
|
||||||
|
|
||||||
|
`AuthorizationFilter`将会按照顺序对这些`pattern/rule`进行处理,并对request执行匹配到的第一个rule。上述配置即意味着`/endpoint`的访问需要用户拥有USE权限,而其他资源的访问则只需要用户经过身份认证即可。
|
||||||
|
|
||||||
|
#### 请求匹配的pattern声明
|
||||||
|
如果需要声明匹配请求的pattern,可以使用`Ant`或是`regex`两种方式。
|
||||||
|
##### Ant
|
||||||
|
Ant是Spirng Security默认的请求匹配方式。通过Ant进行匹配的示例如下:
|
||||||
|
```java
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers("/resource/**").hasAuthority("USER")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
通过Ant,可以声明占位符并且在匹配时进行捕获,以供后续使用,示例如下:
|
||||||
|
```java
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
上述请求匹配中,name占位符对应的值被捕获,并通过捕获的值来判断用户是否拥有访问权限。
|
||||||
|
|
||||||
|
##### regex
|
||||||
|
除了通过Ant来进行匹配外,spring security还支持通过regex来对请求进行匹配。
|
||||||
|
|
||||||
|
如下展示了一个通过regex来进行请求匹配的示例,匹配的是请求地址中包含username,并且username完全由字母和数字组成的请求:
|
||||||
|
```java
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
|
||||||
|
.anyRequest().denyAll()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 通过Http Method来进行匹配
|
||||||
|
spring security中的rules匹配还支持通过Http method来进行匹配。
|
||||||
|
|
||||||
|
如下示例代表所有GET请求都需要拥有read权限,所有的POST请求都需要拥有write权限:
|
||||||
|
```java
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(HttpMethod.GET).hasAuthority("read")
|
||||||
|
.requestMatchers(HttpMethod.POST).hasAuthority("write")
|
||||||
|
.anyRequest().denyAll()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 自定义Matcher来进行匹配
|
||||||
|
如下示例定义了通过自定义Matcher的匹配:
|
||||||
|
```java
|
||||||
|
RequestMatcher printview = (request) -> request.getParameter("print") != null;
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(printview).hasAuthority("print")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Authorizing Request
|
||||||
|
一旦请求匹配了特定的rule,可以通过如下方式来对请求执行鉴权逻辑:
|
||||||
|
- `permitAll`:该请求无需经过权限校验,请求访问的是public endpoint,在这种情况下,Authentication并不会从session中被获取
|
||||||
|
- `denyAll`:该请求在任何场景下都是不允许的,在这种情况下,也不会从session中获取Authentication
|
||||||
|
- `hasAuthority`:该操作需要请求对应的`Authenticaiton`对象所拥有的`GrantedAuthority`集合中,存在指定的权限
|
||||||
|
- `hasRole`:类似于`hasAuthority`,但是在要求的请求中需要补充`ROLE_`前缀,`hasRole("XXX")`等价于`hasAuthority("ROLE_XXX")`
|
||||||
|
- `hasAnyAuthority`:要求请求拥有指定权限集合中的任意一个
|
||||||
|
- `hasRole`:拥有指定角色中的任意一个
|
||||||
|
- `access`:该请求将会使用自定义的AuthorityManager来判断是否拥有权限来进行访问
|
||||||
|
|
||||||
|
如下是一个较为复杂的权限认证示例:
|
||||||
|
```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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 通过Spel表达式来执行权限认证
|
||||||
|
spring security将其所有authorization字段和方法封装在一个root object中,最通用的root object即是`SecurityExpressionRoot`,其是`WebSecurityExpressionRoot`的父类。spring security在执行authorization expression时,将root object应用为`StandardEvaluationContext`。
|
||||||
|
|
||||||
|
##### 使用Authorization表达式
|
||||||
|
root object提供的常用方法如下:
|
||||||
|
- `permitAll`
|
||||||
|
- `denyAll`
|
||||||
|
- `hasAuthority`
|
||||||
|
- `hasRole`
|
||||||
|
- `hasAnyAuthority`
|
||||||
|
- `hasAnyRole`
|
||||||
|
- `hasPermission`
|
||||||
|
|
||||||
|
root object中提供的常用fields如下:
|
||||||
|
- `authentication`:当前关联的Authentidcation对象
|
||||||
|
- `principal`:当前`Authentication#getPrincipal`调用返回的值
|
||||||
|
|
||||||
|
如下示例通过spel指定了认证url
|
||||||
|
```java
|
||||||
|
<http>
|
||||||
|
<intercept-url pattern="/static/**" access="permitAll"/>
|
||||||
|
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/>
|
||||||
|
<intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/>
|
||||||
|
<intercept-url pattern="/**" access="denyAll"/>
|
||||||
|
</http>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 使用path变量
|
||||||
|
如下实例为url指定了path变量,并且通过spel使用了path变量的值:
|
||||||
|
```java
|
||||||
|
<http>
|
||||||
|
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
|
||||||
|
<intercept-url pattern="/**" access="authenticated"/>
|
||||||
|
</http>
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 使用数据库来进行权限认证
|
||||||
|
如果想要使用单独的微服务实例来执行授权操作,可以创建自定义的AuthorizationManager,并且将rule指定为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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
之后,可以通过HttpSecurity配置rule对应的authorizationManager:
|
||||||
|
```java
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
|
||||||
|
http
|
||||||
|
// ...
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().access(authz)
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### SecurityMatcher
|
||||||
|
类似于RequestMatcher,SecurityMatcher用于决定HttpSecurity是否应该被用于该请求,使用示例如下:
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatcher("/api/**")
|
||||||
|
.authorizeHttpRequests(authorize -> authorize
|
||||||
|
.requestMatchers("/user/**").hasRole("USER")
|
||||||
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.formLogin(withDefaults());
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user