1 簡(jiǎn)介
在之前的文章《Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證》講解了如何在傳統(tǒng)的Web項(xiàng)目中整合Spring Security和JWT,今天我們講解如何在響應(yīng)式WebFlux項(xiàng)目中整合。二者大體是相同的,主要區(qū)別在于Reactive WebFlux與傳統(tǒng)Web的區(qū)別。
2 項(xiàng)目整合
引入必要的依賴(lài):
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工具類(lèi)
該工具類(lèi)主要功能是創(chuàng)建、校驗(yàn)、解析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的過(guò)濾器
這個(gè)過(guò)濾器的主要功能是從請(qǐng)求中獲取JWT,然后進(jìn)行校驗(yàn),如何成功則把Authentication放進(jìn)ReactiveSecurityContext里去。當(dāng)然,如果沒(méi)有帶相關(guān)的請(qǐng)求頭,那可能是通過(guò)其它方式進(jìn)行鑒權(quán),則直接放過(guò),讓它進(jìn)入下一個(gè)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的配置
這里設(shè)置了兩個(gè)異常處理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
先判斷對(duì)用戶(hù)密碼進(jìn)行判斷,如果正確則返回對(duì)應(yīng)的權(quán)限用戶(hù),根據(jù)用戶(hù)生成JWT,再返回給客戶(hù)端。
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 總結(jié)
其它與之前的大同小異,不一一講解了。
代碼請(qǐng)查看:https://github.com/LarryDpk/pkslow-samples
以上就是Springboot WebFlux集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的示例的詳細(xì)內(nèi)容,更多關(guān)于Springboot WebFlux集成Spring Security的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://www.pkslow.com/archives/springboot-spring-security-jwt-webflux