经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Springboot WebFlux集成Spring Security实现JWT认证
来源:cnblogs  作者:南瓜慢说  时间:2021/6/28 9:45:10  对本文有异议

我最新最全的文章都在 南瓜慢说 www.pkslow.com ,欢迎大家来喝茶!

1 简介

在之前的文章《Springboot集成Spring Security实现JWT认证》讲解了如何在传统的Web项目中整合Spring SecurityJWT,今天我们讲解如何在响应式WebFlux项目中整合。二者大体是相同的,主要区别在于Reactive WebFlux与传统Web的区别。

2 项目整合

引入必要的依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-webflux</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-security</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>io.jsonwebtoken</groupId>
  11. <artifactId>jjwt</artifactId>
  12. <version>0.9.1</version>
  13. </dependency>

2.1 JWT工具类

该工具类主要功能是创建、校验、解析JWT

  1. @Component
  2. public class JwtTokenProvider {
  3. private static final String AUTHORITIES_KEY = "roles";
  4. private final JwtProperties jwtProperties;
  5. private String secretKey;
  6. public JwtTokenProvider(JwtProperties jwtProperties) {
  7. this.jwtProperties = jwtProperties;
  8. }
  9. @PostConstruct
  10. public void init() {
  11. secretKey = Base64.getEncoder().encodeToString(jwtProperties.getSecretKey().getBytes());
  12. }
  13. public String createToken(Authentication authentication) {
  14. String username = authentication.getName();
  15. Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  16. Claims claims = Jwts.claims().setSubject(username);
  17. if (!authorities.isEmpty()) {
  18. claims.put(AUTHORITIES_KEY, authorities.stream().map(GrantedAuthority::getAuthority).collect(joining(",")));
  19. }
  20. Date now = new Date();
  21. Date validity = new Date(now.getTime() + this.jwtProperties.getValidityInMs());
  22. return Jwts.builder()
  23. .setClaims(claims)
  24. .setIssuedAt(now)
  25. .setExpiration(validity)
  26. .signWith(SignatureAlgorithm.HS256, this.secretKey)
  27. .compact();
  28. }
  29. public Authentication getAuthentication(String token) {
  30. Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
  31. Object authoritiesClaim = claims.get(AUTHORITIES_KEY);
  32. Collection<? extends GrantedAuthority> authorities = authoritiesClaim == null ? AuthorityUtils.NO_AUTHORITIES
  33. : AuthorityUtils.commaSeparatedStringToAuthorityList(authoritiesClaim.toString());
  34. User principal = new User(claims.getSubject(), "", authorities);
  35. return new UsernamePasswordAuthenticationToken(principal, token, authorities);
  36. }
  37. public boolean validateToken(String token) {
  38. try {
  39. Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
  40. if (claims.getBody().getExpiration().before(new Date())) {
  41. return false;
  42. }
  43. return true;
  44. } catch (JwtException | IllegalArgumentException e) {
  45. throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
  46. }
  47. }
  48. }

2.2 JWT的过滤器

这个过滤器的主要功能是从请求中获取JWT,然后进行校验,如何成功则把Authentication放进ReactiveSecurityContext里去。当然,如果没有带相关的请求头,那可能是通过其它方式进行鉴权,则直接放过,让它进入下一个Filter

  1. public class JwtTokenAuthenticationFilter implements WebFilter {
  2. public static final String HEADER_PREFIX = "Bearer ";
  3. private final JwtTokenProvider tokenProvider;
  4. public JwtTokenAuthenticationFilter(JwtTokenProvider tokenProvider) {
  5. this.tokenProvider = tokenProvider;
  6. }
  7. @Override
  8. public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
  9. String token = resolveToken(exchange.getRequest());
  10. if (StringUtils.hasText(token) && this.tokenProvider.validateToken(token)) {
  11. Authentication authentication = this.tokenProvider.getAuthentication(token);
  12. return chain.filter(exchange)
  13. .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
  14. }
  15. return chain.filter(exchange);
  16. }
  17. private String resolveToken(ServerHttpRequest request) {
  18. String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
  19. if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) {
  20. return bearerToken.substring(7);
  21. }
  22. return null;
  23. }
  24. }

2.3 Security的配置

这里设置了两个异常处理authenticationEntryPointaccessDeniedHandler

  1. @Configuration
  2. public class SecurityConfig {
  3. @Bean
  4. SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
  5. JwtTokenProvider tokenProvider,
  6. ReactiveAuthenticationManager reactiveAuthenticationManager) {
  7. return http.csrf(ServerHttpSecurity.CsrfSpec::disable)
  8. .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
  9. .authenticationManager(reactiveAuthenticationManager)
  10. .exceptionHandling().authenticationEntryPoint(
  11. (swe, e) -> {
  12. swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  13. return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("UNAUTHORIZED".getBytes())));
  14. })
  15. .accessDeniedHandler((swe, e) -> {
  16. swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
  17. return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("FORBIDDEN".getBytes())));
  18. }).and()
  19. .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
  20. .authorizeExchange(it -> it
  21. .pathMatchers(HttpMethod.POST, "/auth/login").permitAll()
  22. .pathMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
  23. .pathMatchers(HttpMethod.GET, "/user").hasRole("USER")
  24. .anyExchange().permitAll()
  25. )
  26. .addFilterAt(new JwtTokenAuthenticationFilter(tokenProvider), SecurityWebFiltersOrder.HTTP_BASIC)
  27. .build();
  28. }
  29. @Bean
  30. public ReactiveAuthenticationManager reactiveAuthenticationManager(CustomUserDetailsService userDetailsService,
  31. PasswordEncoder passwordEncoder) {
  32. UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
  33. authenticationManager.setPasswordEncoder(passwordEncoder);
  34. return authenticationManager;
  35. }
  36. }

2.4 获取JWT的Controller

先判断对用户密码进行判断,如果正确则返回对应的权限用户,根据用户生成JWT,再返回给客户端。

  1. @RestController
  2. @RequestMapping("/auth")
  3. public class AuthController {
  4. @Autowired
  5. ReactiveAuthenticationManager authenticationManager;
  6. @Autowired
  7. JwtTokenProvider jwtTokenProvider;
  8. @PostMapping("/login")
  9. public Mono<String> login(@RequestBody AuthRequest request) {
  10. String username = request.getUsername();
  11. Mono<Authentication> authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, request.getPassword()));
  12. return authentication.map(auth -> jwtTokenProvider.createToken(auth));
  13. }
  14. }

3 总结

其它与之前的大同小异,不一一讲解了。

代码请查看:https://github.com/LarryDpk/pkslow-samples


欢迎关注微信公众号<南瓜慢说>,将持续为你更新...

多读书,多分享;多写作,多整理。

原文链接:http://www.cnblogs.com/larrydpk/p/14942951.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号