blog/source/_posts/spring-security-webflux.md

135 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 在Spring WebFlux中的配置Spring Security
tags:
- Java
- Spring
- Spring Security
- Spring WebFlux
- 安全认证
categories:
- - JVM
- Spring
keywords: 'Spring,Spring Security,UserDetails,SecurityContext,Http,WebFlux'
date: 2021-08-11 22:52:00
---
其实在前两篇文章中如果已经明白了Spring Security在Spring MVC中的配置那么再去理解Spring Security在Spring WebFlux中的配置就十分容易了。这是因为在Spring WebFlux中的配置与Spring MVC中的配置原理基本相似只是换了一套类而已。<!-- more -->
与在Spring MVC中一样要完成Spring Security配置需要至少完成以下两项内容
1. 完成设置HTTP的认证流程。
1. 完成认证所需要使用的类。
以下将从这两个方向分别说明如何在WebFlux应用中配置Spring Security。
1. {% post_link spring-security-basic %}
1. {% post_link spring-security-webmvc %}
1. {% post_link spring-security-webflux %}
## 配置类
相比Spring MVCWebFlux中的配置类已经变得十分简单了已经被简化成了一个非常普通的使用`@Configuration`标记的Bean而WebFlux的配置方法也是只需要形成一个`SecurityWebFilterChain`类的Bean即可。要形成`SecurityWebFilterChain`类的Bean只需要使用`ServerHttpSecurity`类的实例的`build()`方法即可。
以下是一个比较简单的配置类示例。
```java
@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
}
```
从这个小示例可以看出,`ServerHttpSecurity`类是WebFlux中完成Spring Security配置的核心类。所以接下来看一下这个类提供的主要功能都有哪些。
{% oss_image spring-security/spring-security-ServerHttpSecurity.svg ServerHttpSecurity类结构图 %}
!!! note ""
在这个图中,同样省略了返回`ServerHttpSecurity`的采用`Customizer`进行配置的方法,其使用方法与同名配置方法无异。
从`ServerHttpSecurity`类的结构图中可以看出来相比Spring MVC中的`HttpSecurity`已经简化了很多使用方法。但是不管发生了什么样的变化在完成Spring Security配置的过程和所需要配置的内容是不变的依旧要按照功能需要进行组合。
## WebFilter
`WebFilter`接口是WebFlux中的重要功能承担着与Spring MVC框架中Servlet Filter相同的功能。Spring Security在WebFlux中功能的实现也是依靠多种实现了`WebFilter`接口的类。
其实与Spring MVC相似Spring Security的`ServerHttpSecurity`类中也提供了将自定义WebFilter加入到请求处理流程中的功能这就是`addFilterAt()`、`addFilterBefore()`、`addFilterAfter()`三个方法。但是与配置Spring MVC中的Servlet Filter顺序不同的是WebFlux中的WebFilter已经被定义好了顺序都保存在了`SecurityWebFilterOrder`枚举里所以在加入自定义WebFilter的时候可以直接选择指定的位置即可不必再去寻找实际的Filter类。
Spring Security中定义的用于Security处理的WebFilter没有什么特殊的完全就是普通的`WebFilter`接口的实现。这个接口被简化一下以后,就是这个样子。
```java
public interface WebFilter {
Mono<void> filter(ServerWebExchange exchange, WebFilterChain chain);
}
```
这里需要对WebFlux中的Exchange的概念进行一下解释。在WebFlux中请求和相应都是被保存在`ServerWebExchange`实例中的。所以所有定义的WebFilter也都是对传入的`ServerWebExchange`实例进行修改和调整。除此之外,`ServerWebExchange`中还包括了`WebSession`和`Principal`等通常在处理Web请求时所需要用到的内容。
## ReactiveAuthenticationManager
在之前的文章中也提到过WebFlux中不同的认证过滤器都采用了不同的AuthenticationManager与Spring MVC不同的是WebFlux中的AuthenticationManager需要实现的是`ReactiveAuthenticationManager`接口这个接口的形式与用于Spring MVC的`AuthenticationManager`接口的形式非常类似,但是还有相当的不同。
以下是这个接口被简化以后的样子。
```java
@FunctionalInterface
public interface ReactiveAuthenticationManager {
Mono<Authentication> authenticate(Authentication authentication);
}
```
这是一个函数式接口也就是说我们在实现这个接口的时候可以直接使用一个Lambda表达式来代替一个类作为这个接口的实现。这也是WebFlux中函数式编程概念的体现。这个借口所返回的`Authentication`实例与Spring Security在Spring MVC中的特性一样都是增加了对于用户认证信息和认证结果的注入。
利用WebFlux中不同的WebFilter会使用的不同的AuthenticationManager的特点就可以在Spring中通过直接定义不同的Bean来为不同的WebFilter提供服务了。
### UserDetailsService
在许多教程中,都会创建一个`ReactiveUserDetailsService`接口的实例这个接口的功能与用于Spring MVC的`UserDetailsService`接口的功能一样但是接口中方法的返回值换成了WebFlux中的`Mono<UserDetails>`。
现在这个接口被简化以后是下面这个样子。
```java
public interface ReactiveUserDetailsService {
Mono<UserDetails> findByUsername(String username);
}
```
在这些教程中,主要是通过`ServerHttpSecurityConfiguration`类用到了`UserDetailsRepositoryReactiveAuthenticationManager`而这个被用到的AuthenticationManager类又依赖到了一个实现了`ReactiveUserDetailsService`接口的Bean。这样就可以为整个登录认证过程提供一个默认的获取用户详细信息的方法。
另一个接口名称跟`ReactiveUserDetailsService`比较相似的是`ReactiveUserDetailsPasswordService`接口。但是这个接口的功能却不是用于获取用户详细信息或者是对用户进行认证,而是用于修改密码。这个接口的定义如下。
```java
public interface ReactiveUserDetailsPasswordService {
Mono<UserDetails> updatePassword(UserDetails user, String newPassword);
}
```
所以这两个接口在使用的时候,不要弄混。`ReactiveUserDetailsPasswordService`接口的实例也是在`ServerHttpSecurityConfiguration`类中被使用的所以也可以通过定义一个Bean来在应用中使用它。
!!! note ""
与此相同的还有`PasswordEncoder`它也是可以通过定义一个Bean来在应用中使用。
### ServerSecurityContextRepository
不止在`ServerHttpSecurity`类中,还有其他的不少类中都可以看到`ServerSecurityContextRepository`接口的身影。这个接口用于从请求信息中载入验证信息也就是从请求中获取Token并组装成Authentication并完成认证过程也可以用于将保存了认证信息的`SecurityContext`保存起来。
`ServerSecurityContextRepository`接口的定义可以简化成以下样子。
```java
public interface ServerSecurityContextRepository {
Mono<Void> save(ServerWebExchange exchange, SecurityContext context);
Mono<SecurityContext> load(SeerverWebExchange exchange);
}
```
从这个接口的定义可以看出Spring Security在保存SecurityContext实例的时候是将其与`SeerverWebExchange`相对应着保存的,也就是说,通过一个`ServerWebExchange`的实例,可以获取到相应的`SecurityContext`,也就获得了其中保存的`Authentication`等内容。