Spring Webflux如何与SpringSecurity结合,实现前后端分离的无状态REST服务?

2026-05-05 16:391阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1722个文字,预计阅读时间需要7分钟。

Spring Webflux如何与SpringSecurity结合,实现前后端分离的无状态REST服务?

源码传送门:https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-stateless-webflux

一、前言

Spring WebFlux 是一个异步非阻塞的 Web 框架,可以充分利用多核 CPU 的硬件资源。以下是关于 Spring WebFlux 的简要介绍。

源码传送门: github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-stateless-webflux 一、前言

Spring WebFlux 是一个异步非阻塞式的 Web 框架,它能够充分利用多核 CPU 的硬件资源去处理大量的并发请求。SpringSecurity 专门为 Webflux 定制了一套用于权限控制的 API,因此在 Webflux 应用中集成
SpringSecurity,和前面讲的 Web 应用集成 SpringSecurity 还是有一定区别。老规矩,我们先看实现步骤,后续再来分析原理。

二、实现步骤 1、引入依赖

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.1</version> </dependency> </dependencies> 2、改写 Get 方法的 /login 请求

和 Spring Web 集成 SpringSecurity 一样,Spring Webflux 集成 SpringSecurity 也需要改写默认的 Get 方法 /login 请求,但是有个小地方要注意下,就是在 Webflux 中要返回 Mono。源码如下:

@GetMapping(value = "/login") public Mono<Result> login() { return Mono.just(Result.data(-1, "PLEASE LOGIN", "NO LOGIN")); }

Result 类是定义的一个通用响应对象,具体代码可查看附上的源码链接。

3、创建认证信息存储器 AuthenticationRepository

在实际生产环境中,我们应该把认证信息存储在缓存或者数据库中,此处只是演示,就放在内存中了。具体代码如下:

@Repository public class AuthenticationRepository { private static ConcurrentHashMap<String, Authentication> authentications = new ConcurrentHashMap<>(); public void add(String key, Authentication authentication) { authentications.put(key, authentication); } public Authentication get(String key) { return authentications.get(key); } public void delete(String key) { if (authentications.containsKey(key)) { authentications.remove(key); } } } 4、创建认证成功处理器 TokenServerAuthenticationSuccessHandler 和认证失败处理器 TokenServerAuthenticationFailureHandler

对于 Webflux 应用 SpringSecurity 为我们提供了不同的认证成功接口 ServerAuthenticationSuccessHandler 和 认证失败处理接口 ServerAuthenticationFailureHandler,我们只需要实现这两个接口,然后实现我们需要的业务逻辑即可,具体代码如下:

@Component public class TokenServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { String token = IdUtil.simpleUUID(); authenticationRepository.add(token, authentication); Result<String> result = Result.data(token, "LOGIN SUCCESS"); return ServerHttpResponseUtils.print(webFilterExchange.getExchange().getResponse(), result); } }

@Component public class TokenServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { @Override public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { Result<String> result = Result.data(-1, exception.getMessage(), "LOGIN FAILED"); return ServerHttpResponseUtils.print(webFilterExchange.getExchange().getResponse(), result); } }

ServerHttpResponseUtils 是封装的一个通过 ServerHttpResponse 向前端响应 JSON 数据格式的工具类,具体代码可查看附上的源码链接。

5、创建退出成功处理器 TokenServerLogoutSuccessHandler

@Component public class TokenServerLogoutSuccessHandler implements ServerLogoutSuccessHandler { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { String token = exchange.getExchange().getRequest().getHeaders().getFirst("token"); if (StrUtil.isNotEmpty(token)) { authenticationRepository.delete(token); } Result<String> result = Result.data(200, "LOGOUT SUCCESS", "OK"); return ServerHttpResponseUtils.print(exchange.getExchange().getResponse(), result); } } 6、创建无访问权限处理器 TokenServerAccessDeniedHandler

public class TokenServerAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) { Result<String> result = Result.data(403, denied.getMessage(), "ACCESS DENIED"); return ServerHttpResponseUtils.print(exchange.getResponse(), result); } } 7、创建 SpringSecurity 上下文仓库 TokenServerSecurityContextRepository

和 Web 应用集成 SpringSecurity 不同的是,SpringSecurity 暂时没有为 Webflux 提供无状态的 SpringSecurity 上下文存取策略。目前 ServerSecurityContextRepository 接口暂时只有
NoOpServerSecurityContextRepository(不存储 SecurityContext) 和 WebSessionServerSecurityContextRepository (基于 WebSession)两种实现策略。因此,我们要想实现无状态的
SpringSecurity 上下文存取,需要我们自己去实现 ServerSecurityContextRepository 接口。源码如下:

@Component public class TokenServerSecurityContextRepository implements ServerSecurityContextRepository { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) { return Mono.empty(); } @Override public Mono<SecurityContext> load(ServerWebExchange exchange) { String token = exchange.getRequest().getHeaders().getFirst("token"); if (StrUtil.isNotEmpty(token)) { Authentication authentication = authenticationRepository.get(token); if (ObjectUtil.isNotEmpty(authentication)) { SecurityContextImpl securityContext = new SecurityContextImpl(); securityContext.setAuthentication(authentication); return Mono.just(securityContext); } } return Mono.empty(); } } 8、配置 WebFluxSecurityConfig,这是重点!!!

创建 WebFluxSecurityConfig 类,并配置 SecurityWebFilterChain Bean对象,对于 Webflux 应用 SpringSecurity 是通过 ServerHttpSecurity 配置各项属性,具体配置如下:

Spring Webflux如何与SpringSecurity结合,实现前后端分离的无状态REST服务?

// Webflux 中使用的注解是不一样的哦 @EnableWebFluxSecurity @EnableReactiveMethodSecurity @Bean public MapReactiveUserDetailsService userDetailsService() { // 权限配置 List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("index")); authorities.add(new SimpleGrantedAuthority("hasAuthority")); authorities.add(new SimpleGrantedAuthority("ROLE_hasRole")); // 认证信息 UserDetails userDetails = User.builder().username("admin") .passwordEncoder(passwordEncoder()::encode) .password("123456") .authorities(authorities) .build(); return new MapReactiveUserDetailsService(userDetails); } @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity localhost:8080/index // curl curl --location --request GET 'localhost:8080/index' // 响应结果 { "code": -1, "msg": "NO LOGIN", "time": 1654524412270, "data": "PLEASE LOGIN" } 2、登录 API

// 请求地址 POST请求 localhost:8080/login // curl curl --location --request POST 'localhost:8080/login' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'username=admin' \ --data-urlencode 'password=123456' // 响应结果 { "code": 200, "msg": "LOGIN SUCCESS", "time": 1654524449600, "data": "80b60ffc7e2b419f9a8e7d8dec355e02" } 3、携带 token 访问受保护 API

// 请求地址 GET请求 请求头中添加认证 token localhost:8080/index // curl curl --location --request GET 'localhost:8080/index' --header 'token: 80b60ffc7e2b419f9a8e7d8dec355e02' // 响应结果 index 4、携带 token 访问未授权 API

// 请求地址 GET请求 请求头中添加认证 token localhost:8080/home // curl curl --location --request GET 'localhost:8080/home' --header 'token: 612c29a2dd824191b6afe07a38285e81' // 响应结果 { "code": 403, "msg": "ACCESS DENIED", "time": 1654524759366, "data": "Denied" } 5、退出 API

// 请求地址 POST请求 请求头中添加认证 token localhost:8080/logout // curl curl --location --request POST 'localhost:8080/logout' --header 'token: 612c29a2dd824191b6afe07a38285e81' // 响应结果 { "code": 200, "msg": "OK", "time": 1654524806801, "data": "LOGOUT SUCCESS" } 四、总结

有了基于 Spring Web 集成 SpringSecurity 经验,根据类比的思想,实现 Spring Webflux 集成 SpringSecurity 不算困难。经过简单的改造之后,基本能满足前后端分离无状态 API 权限控制的需求。但是,在应用于生产环境前,有两点需要进一步改造:

1、将身份认证和权限获取,改为从数据库中获取。

2、将通过认证的身份信息存储在缓存或数据库中。

在下一篇,我们将进一步分析 Spring Webflux 集成 SpringSecurity 的实现原理,大家多多关注哦~

推荐下个人的基于 SpringCloud 开源项目,供大家学习参考,欢迎大家留言进群交流

Gitee:gitee.com/ningzxspace/exam-ning-springcloud-v1

Github:github.com/ningzuoxin/exam-ning-springcloud-v1

本文共计1722个文字,预计阅读时间需要7分钟。

Spring Webflux如何与SpringSecurity结合,实现前后端分离的无状态REST服务?

源码传送门:https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-stateless-webflux

一、前言

Spring WebFlux 是一个异步非阻塞的 Web 框架,可以充分利用多核 CPU 的硬件资源。以下是关于 Spring WebFlux 的简要介绍。

源码传送门: github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-stateless-webflux 一、前言

Spring WebFlux 是一个异步非阻塞式的 Web 框架,它能够充分利用多核 CPU 的硬件资源去处理大量的并发请求。SpringSecurity 专门为 Webflux 定制了一套用于权限控制的 API,因此在 Webflux 应用中集成
SpringSecurity,和前面讲的 Web 应用集成 SpringSecurity 还是有一定区别。老规矩,我们先看实现步骤,后续再来分析原理。

二、实现步骤 1、引入依赖

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.1</version> </dependency> </dependencies> 2、改写 Get 方法的 /login 请求

和 Spring Web 集成 SpringSecurity 一样,Spring Webflux 集成 SpringSecurity 也需要改写默认的 Get 方法 /login 请求,但是有个小地方要注意下,就是在 Webflux 中要返回 Mono。源码如下:

@GetMapping(value = "/login") public Mono<Result> login() { return Mono.just(Result.data(-1, "PLEASE LOGIN", "NO LOGIN")); }

Result 类是定义的一个通用响应对象,具体代码可查看附上的源码链接。

3、创建认证信息存储器 AuthenticationRepository

在实际生产环境中,我们应该把认证信息存储在缓存或者数据库中,此处只是演示,就放在内存中了。具体代码如下:

@Repository public class AuthenticationRepository { private static ConcurrentHashMap<String, Authentication> authentications = new ConcurrentHashMap<>(); public void add(String key, Authentication authentication) { authentications.put(key, authentication); } public Authentication get(String key) { return authentications.get(key); } public void delete(String key) { if (authentications.containsKey(key)) { authentications.remove(key); } } } 4、创建认证成功处理器 TokenServerAuthenticationSuccessHandler 和认证失败处理器 TokenServerAuthenticationFailureHandler

对于 Webflux 应用 SpringSecurity 为我们提供了不同的认证成功接口 ServerAuthenticationSuccessHandler 和 认证失败处理接口 ServerAuthenticationFailureHandler,我们只需要实现这两个接口,然后实现我们需要的业务逻辑即可,具体代码如下:

@Component public class TokenServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { String token = IdUtil.simpleUUID(); authenticationRepository.add(token, authentication); Result<String> result = Result.data(token, "LOGIN SUCCESS"); return ServerHttpResponseUtils.print(webFilterExchange.getExchange().getResponse(), result); } }

@Component public class TokenServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { @Override public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { Result<String> result = Result.data(-1, exception.getMessage(), "LOGIN FAILED"); return ServerHttpResponseUtils.print(webFilterExchange.getExchange().getResponse(), result); } }

ServerHttpResponseUtils 是封装的一个通过 ServerHttpResponse 向前端响应 JSON 数据格式的工具类,具体代码可查看附上的源码链接。

5、创建退出成功处理器 TokenServerLogoutSuccessHandler

@Component public class TokenServerLogoutSuccessHandler implements ServerLogoutSuccessHandler { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { String token = exchange.getExchange().getRequest().getHeaders().getFirst("token"); if (StrUtil.isNotEmpty(token)) { authenticationRepository.delete(token); } Result<String> result = Result.data(200, "LOGOUT SUCCESS", "OK"); return ServerHttpResponseUtils.print(exchange.getExchange().getResponse(), result); } } 6、创建无访问权限处理器 TokenServerAccessDeniedHandler

public class TokenServerAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) { Result<String> result = Result.data(403, denied.getMessage(), "ACCESS DENIED"); return ServerHttpResponseUtils.print(exchange.getResponse(), result); } } 7、创建 SpringSecurity 上下文仓库 TokenServerSecurityContextRepository

和 Web 应用集成 SpringSecurity 不同的是,SpringSecurity 暂时没有为 Webflux 提供无状态的 SpringSecurity 上下文存取策略。目前 ServerSecurityContextRepository 接口暂时只有
NoOpServerSecurityContextRepository(不存储 SecurityContext) 和 WebSessionServerSecurityContextRepository (基于 WebSession)两种实现策略。因此,我们要想实现无状态的
SpringSecurity 上下文存取,需要我们自己去实现 ServerSecurityContextRepository 接口。源码如下:

@Component public class TokenServerSecurityContextRepository implements ServerSecurityContextRepository { @Autowired private AuthenticationRepository authenticationRepository; @Override public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) { return Mono.empty(); } @Override public Mono<SecurityContext> load(ServerWebExchange exchange) { String token = exchange.getRequest().getHeaders().getFirst("token"); if (StrUtil.isNotEmpty(token)) { Authentication authentication = authenticationRepository.get(token); if (ObjectUtil.isNotEmpty(authentication)) { SecurityContextImpl securityContext = new SecurityContextImpl(); securityContext.setAuthentication(authentication); return Mono.just(securityContext); } } return Mono.empty(); } } 8、配置 WebFluxSecurityConfig,这是重点!!!

创建 WebFluxSecurityConfig 类,并配置 SecurityWebFilterChain Bean对象,对于 Webflux 应用 SpringSecurity 是通过 ServerHttpSecurity 配置各项属性,具体配置如下:

Spring Webflux如何与SpringSecurity结合,实现前后端分离的无状态REST服务?

// Webflux 中使用的注解是不一样的哦 @EnableWebFluxSecurity @EnableReactiveMethodSecurity @Bean public MapReactiveUserDetailsService userDetailsService() { // 权限配置 List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("index")); authorities.add(new SimpleGrantedAuthority("hasAuthority")); authorities.add(new SimpleGrantedAuthority("ROLE_hasRole")); // 认证信息 UserDetails userDetails = User.builder().username("admin") .passwordEncoder(passwordEncoder()::encode) .password("123456") .authorities(authorities) .build(); return new MapReactiveUserDetailsService(userDetails); } @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity localhost:8080/index // curl curl --location --request GET 'localhost:8080/index' // 响应结果 { "code": -1, "msg": "NO LOGIN", "time": 1654524412270, "data": "PLEASE LOGIN" } 2、登录 API

// 请求地址 POST请求 localhost:8080/login // curl curl --location --request POST 'localhost:8080/login' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'username=admin' \ --data-urlencode 'password=123456' // 响应结果 { "code": 200, "msg": "LOGIN SUCCESS", "time": 1654524449600, "data": "80b60ffc7e2b419f9a8e7d8dec355e02" } 3、携带 token 访问受保护 API

// 请求地址 GET请求 请求头中添加认证 token localhost:8080/index // curl curl --location --request GET 'localhost:8080/index' --header 'token: 80b60ffc7e2b419f9a8e7d8dec355e02' // 响应结果 index 4、携带 token 访问未授权 API

// 请求地址 GET请求 请求头中添加认证 token localhost:8080/home // curl curl --location --request GET 'localhost:8080/home' --header 'token: 612c29a2dd824191b6afe07a38285e81' // 响应结果 { "code": 403, "msg": "ACCESS DENIED", "time": 1654524759366, "data": "Denied" } 5、退出 API

// 请求地址 POST请求 请求头中添加认证 token localhost:8080/logout // curl curl --location --request POST 'localhost:8080/logout' --header 'token: 612c29a2dd824191b6afe07a38285e81' // 响应结果 { "code": 200, "msg": "OK", "time": 1654524806801, "data": "LOGOUT SUCCESS" } 四、总结

有了基于 Spring Web 集成 SpringSecurity 经验,根据类比的思想,实现 Spring Webflux 集成 SpringSecurity 不算困难。经过简单的改造之后,基本能满足前后端分离无状态 API 权限控制的需求。但是,在应用于生产环境前,有两点需要进一步改造:

1、将身份认证和权限获取,改为从数据库中获取。

2、将通过认证的身份信息存储在缓存或数据库中。

在下一篇,我们将进一步分析 Spring Webflux 集成 SpringSecurity 的实现原理,大家多多关注哦~

推荐下个人的基于 SpringCloud 开源项目,供大家学习参考,欢迎大家留言进群交流

Gitee:gitee.com/ningzxspace/exam-ning-springcloud-v1

Github:github.com/ningzuoxin/exam-ning-springcloud-v1