Springboot WebFlux集成Spring Security实现JWT认证的示例

 更新时间:2021年04月01日 08:55:03   作者:南瓜慢说  
这篇文章主要介绍了Springboot WebFlux集成Spring Security实现JWT认证的示例,帮助大家更好的理解和学习使用springboot框架,感兴趣的朋友可以了解下

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

以上就是Springboot WebFlux集成Spring Security实现JWT认证的示例的详细内容,更多关于Springboot WebFlux集成Spring Security的资料请关注脚本之家其它相关文章!

相关文章

  • 浅谈Spring Security 对于静态资源的拦截与放行

    浅谈Spring Security 对于静态资源的拦截与放行

    这篇文章主要介绍了浅谈Spring Security 对于静态资源的拦截与放行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Spring Cloud Feign请求添加headers的实现方式

    Spring Cloud Feign请求添加headers的实现方式

    这篇文章主要介绍了Spring Cloud Feign请求添加headers的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Activiti流程图查看实例

    Activiti流程图查看实例

    这篇文章主要介绍了Activiti流程图查看实例,需要的朋友可以参考下
    2014-08-08
  • SpringSecurity-2.7中跨域问题解析

    SpringSecurity-2.7中跨域问题解析

    这篇文章主要介绍了SpringSecurity-2.7中跨域问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-01-01
  • java 中clone()的使用方法

    java 中clone()的使用方法

    这篇文章主要介绍了java 中clone()的使用方法的相关资料,希望通过本文能帮助大家能掌握clone()的克隆方法,需要的朋友可以参考下
    2017-09-09
  • MyBatis数据脱敏的实现方案介绍

    MyBatis数据脱敏的实现方案介绍

    在我们数据库中有些时候会保存一些用户的敏感信息,比如:手机号、银行卡等信息,如果这些信息以明文的方式保存,那么是不安全的
    2022-08-08
  • 如何使用IDEA查看java文件编译后的字节码内容

    如何使用IDEA查看java文件编译后的字节码内容

    这篇文章主要介绍了如何使用IDEA查看java文件编译后的字节码内容,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java中transient关键字的详细总结

    Java中transient关键字的详细总结

    本文要介绍的是Java中的transient关键字,transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略,感兴趣的朋友可以参考阅读
    2023-04-04
  • Springboot 整合 Java DL4J 实现医学影像诊断功能介绍

    Springboot 整合 Java DL4J 实现医学影像诊断功能介绍

    本文介绍如何利用SpringBoot整合Java Deeplearning4j实现医学影像诊断功能,重点介绍了卷积神经网络在处理医学影像中的应用,以及如何进行数据预处理、模型构建、训练与预测,提供了详细的代码实现和单元测试方法,目的是辅助医生更准确快速地进行疾病诊断
    2024-10-10
  • java IP地址网段计算的示例代码

    java IP地址网段计算的示例代码

    这篇文章主要介绍了java IP地址网段计算的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论