1 简介
在之前的文章《Springboot集成Spring Security实现JWT认证》讲解了如何在传统的Web项目中整合Spring Security和JWT,今天我们讲解如何在响应式WebFlux项目中整合。二者大体是相同的,主要区别在于Reactive WebFlux与传统Web的区别。
2 项目整合
引入必要的依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
< 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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
@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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@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,再返回给客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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
以上就是Springboot WebFlux集成Spring Security实现JWT认证的示例的详细内容,更多关于Springboot WebFlux集成Spring Security的资料请关注服务器之家其它相关文章!
原文链接:https://www.pkslow.com/archives/springboot-spring-security-jwt-webflux