Springboot WebFlux集成Spring Security实现JWT认证的示例
作者:南瓜慢说 发布时间:2021-06-02 03:24:50
标签:Springboot,WebFlux,Spring,Security,jwt认证
目录
1 简介
2 项目整合
2.1 JWT工具类
2.2 JWT的过滤器
2.3 Security的配置
2.4 获取JWT的Controller
3 总结
1 简介
在之前的文章《Springboot集成Spring Security实现JWT认证》讲解了如何在传统的Web项目中整合Spring Security和JWT,今天我们讲解如何在响应式WebFlux项目中整合。二者大体是相同的,主要区别在于Reactive WebFlux与传统Web的区别。
2 项目整合
引入必要的依赖:
<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>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.1 JWT工具类
该工具类主要功能是创建、校验、解析JWT。
@Component
public class JwtTokenProvider {
private static final String AUTHORITIES_KEY = "roles";
private final JwtProperties jwtProperties;
private String secretKey;
public JwtTokenProvider(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}
@PostConstruct
public void init() {
secretKey = Base64.getEncoder().encodeToString(jwtProperties.getSecretKey().getBytes());
}
public String createToken(Authentication authentication) {
String username = authentication.getName();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Claims claims = Jwts.claims().setSubject(username);
if (!authorities.isEmpty()) {
claims.put(AUTHORITIES_KEY, authorities.stream().map(GrantedAuthority::getAuthority).collect(joining(",")));
}
Date now = new Date();
Date validity = new Date(now.getTime() + this.jwtProperties.getValidityInMs());
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, this.secretKey)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
Object authoritiesClaim = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities = authoritiesClaim == null ? AuthorityUtils.NO_AUTHORITIES
: AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesClaim.toString());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
2.2 JWT的过滤器
这个过滤器的主要功能是从请求中获取JWT,然后进行校验,如何成功则把Authentication放进ReactiveSecurityContext里去。当然,如果没有带相关的请求头,那可能是通过其它方式进行鉴权,则直接放过,让它进入下一个Filter。
public class JwtTokenAuthenticationFilter implements WebFilter {
public static final String HEADER_PREFIX = "Bearer ";
private final JwtTokenProvider tokenProvider;
public JwtTokenAuthenticationFilter(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String token = resolveToken(exchange.getRequest());
if (StringUtils.hasText(token) && this.tokenProvider.validateToken(token)) {
Authentication authentication = this.tokenProvider.getAuthentication(token);
return chain.filter(exchange)
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
}
return chain.filter(exchange);
}
private String resolveToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
}
2.3 Security的配置
这里设置了两个异常处理authenticationEntryPoint和accessDeniedHandler。
@Configuration
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
JwtTokenProvider tokenProvider,
ReactiveAuthenticationManager reactiveAuthenticationManager) {
return http.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.authenticationManager(reactiveAuthenticationManager)
.exceptionHandling().authenticationEntryPoint(
(swe, e) -> {
swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("UNAUTHORIZED".getBytes())));
})
.accessDeniedHandler((swe, e) -> {
swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("FORBIDDEN".getBytes())));
}).and()
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.authorizeExchange(it -> it
.pathMatchers(HttpMethod.POST, "/auth/login").permitAll()
.pathMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
.pathMatchers(HttpMethod.GET, "/user").hasRole("USER")
.anyExchange().permitAll()
)
.addFilterAt(new JwtTokenAuthenticationFilter(tokenProvider), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager(CustomUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
authenticationManager.setPasswordEncoder(passwordEncoder);
return authenticationManager;
}
}
2.4 获取JWT的Controller
先判断对用户密码进行判断,如果正确则返回对应的权限用户,根据用户生成JWT,再返回给客户端。
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
ReactiveAuthenticationManager authenticationManager;
@Autowired
JwtTokenProvider jwtTokenProvider;
@PostMapping("/login")
public Mono<String> login(@RequestBody AuthRequest request) {
String username = request.getUsername();
Mono<Authentication> authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, request.getPassword()));
return authentication.map(auth -> jwtTokenProvider.createToken(auth));
}
}
3 总结
其它与之前的大同小异,不一一讲解了。
代码请查看:https://github.com/LarryDpk/pkslow-samples
来源:https://www.pkslow.com/archives/springboot-spring-security-jwt-webflux


猜你喜欢
- 写完js倒计时,突然想用java实现倒计时,写了三种实现方式一:设置时长的倒计时;二:设置时间戳的倒计时;三:使用java.util.Tim
- 我发现网上许多讲解javaweb 项目 SSM(Spring,SpringMVC,Mybatis)配置的时候有些重点没有提到,一下我会贴上一
- 今天在使用Nlog的时候,发现了一个之前没注意的问题。以前,我的app配置文件都是这么写的,当然配置比较多的时候会改用xml。 如
- springboot+spring data jpa实现新增及批量新增spring data jpa (以下简称jpa)。这个orm其实和m
- yml配置规则属性跟属性值之间使用“:”和一个“空格”隔开,
- 单例模式是23种设计模式之一,是比较简单的一种设计模式,它的目的是无论调用多少次,都返回同一个对象,它的特点是构造器私有化。它分为两种结构,
- 提出问题下面所给代码编译时正常,但是执行时会出错,请指出程序在执行时能够执行到编号为(1)(2)(3)的代码行中的哪一行。using Sys
- 多数据源配置首先是配置文件这里采用yml配置文件,其他类型配置文件同理我配置了两个数据源,一个名字叫ds1数据源,一个名字叫ds2数据源,如
- 前言兄弟们,刚刚又给seata社区修了一个BUG,有用户提了issue反应TransactionHook在某些情况下不会被调用:相关issu
- 写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢?讲讲字
- 做Android开发两年的时间,技术稍稍有一些提升,刚好把自己实现的功能写出来,记录一下,如果能帮助到同行的其他人,我也算是做了件好事,哈哈
- 目录1、数组本质2、指针3、字符数组4、char * 与 char a[ ]5、char ** 和char *a[]6、C语言中char s
- 404这个错误真的是一言难尽!不过大多是配置文件出错,认真修改还是可以的1.web.xml配置错误:默认首页没有写的,在web.xml添加一
- @Value注解设置默认值在Spring Boot中,如果使用@Value注解对属性进行赋值,但如果在配置文件或启动参数中未指定对应的参数值
- 要实现PPT转图片,首先需要引用两个DLL。我这里用的这个这个版本Microsoft.Office.Interop.PowerPoint 1
- 先来看看下面List<T>泛型集合的排序例子:using System;using System.Collections.Gen
- package com.yswc.dao.sign;import java.io.BufferedReader;import java.io
- 我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通
- 本文实例讲述了Winform中Treeview实现按需加载的方法,非常具有实用价值。分享给大家供大家参考。具体分析如下:最近项目里用到tre
- 前言文件上传是项目开发中最常见的功能之一 ,SpringMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配Multi