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

目录1 简介2 项目整合2.1 JWT工具类2.2 JWT的过滤器2.3 Security的配置2.4 获取JWT的Controller3 总结

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。

@Componentpublic 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。

@Configurationpublic 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的资料请关注其它相关文章!

谁是谁生命的点缀。

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

相关文章:

你感兴趣的文章:

标签云: