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

作者:南瓜慢说 时间:2021-06-02 03:24:50 

目录
  • 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

    标签:Springboot,WebFlux,Spring,Security,jwt认证
    0
    投稿

    猜你喜欢

  • Java类加载机制实现流程及原理详解

    2022-05-26 02:53:24
  • 配置springboot项目使用外部tomcat过程解析

    2021-11-22 22:34:25
  • 使用@ConfigurationProperties实现类型安全的配置过程

    2023-07-01 00:26:05
  • C#静态方法的使用

    2022-05-19 06:45:09
  • JAVA使用Gson解析json数据实例解析

    2021-08-23 14:50:56
  • 10道springboot常见面试题

    2023-09-02 03:02:22
  • 使用spring容器在初始化Bean时前和后的操作

    2021-07-01 05:49:24
  • Maven构建生命周期详细介绍

    2023-04-06 13:02:59
  • java自定义注解实现前后台参数校验的实例

    2023-04-27 23:53:21
  • C# 得到某一天的起始和截止时间的代码

    2021-11-13 10:57:54
  • Android 蓝牙BLE开发完全指南

    2023-07-03 03:15:06
  • java读取csv文件内容示例代码

    2023-03-13 22:09:14
  • java编译器和JVM的区别

    2023-07-18 20:34:48
  • Android中使用Toast.cancel()方法优化toast内容显示的解决方法

    2021-12-14 05:17:03
  • Android mvvm之LiveData原理案例详解

    2023-09-29 11:49:26
  • Java Spring 事件监听详情解析

    2021-06-05 00:02:08
  • springboot快速整合Mybatis组件的方法(推荐)

    2022-03-23 11:59:15
  • Android带进度条的文件上传示例(使用AsyncTask异步任务)

    2023-06-24 09:43:11
  • Android 调用发送短信的方法

    2023-05-30 22:56:40
  • java 线程锁详细介绍及实例代码

    2022-03-16 13:50:45
  • asp之家 软件编程 m.aspxhome.com