阅读spring security文档

This commit is contained in:
asahi
2024-03-22 21:04:16 +08:00
parent 82a6acfe5e
commit df5101160b

View File

@@ -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
类似于RequestMatcherSecurityMatcher用于决定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();
}
}
```