Springboot WebFlux集成Spring Security實現(xiàn)JWT認證的示例
1 簡介
在之前的文章《Springboot集成Spring Security實現(xiàn)JWT認證》講解了如何在傳統(tǒng)的Web項目中整合Spring Security和JWT,今天我們講解如何在響應式WebFlux項目中整合。二者大體是相同的,主要區(qū)別在于Reactive WebFlux與傳統(tǒng)Web的區(qū)別。
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工具類
該工具類主要功能是創(chuàng)建、校驗、解析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
先判斷對用戶密碼進行判斷,如果正確則返回對應的權限用戶,根據(jù)用戶生成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實現(xiàn)JWT認證的示例的詳細內(nèi)容,更多關于Springboot WebFlux集成Spring Security的資料請關注腳本之家其它相關文章!
相關文章
Java SpringBoot快速集成SpringBootAdmin管控臺監(jiān)控服務詳解
這篇文章主要介紹了如何基于springboot-admin管控臺監(jiān)控服務,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2021-09-09Spring Boot實現(xiàn)分布式鎖的自動釋放的示例代碼
在實際開發(fā)中,我們可以使用 Redis、Zookeeper 等分布式系統(tǒng)來實現(xiàn)分布式鎖,本文將介紹如何使用 Spring Boot 來實現(xiàn)分布式鎖的自動釋放,感興趣的朋友跟隨小編一起看看吧2023-06-06Hadoop之NameNode Federation圖文詳解
今天小編就為大家分享一篇關于Hadoop之NameNode Federation圖文詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01SpringMVC MethodArgumentResolver的作用與實現(xiàn)
這篇文章主要介紹了SpringMVC MethodArgumentResolver的作用與實現(xiàn),MethodArgumentResolver采用一種策略模式,在Handler的方法被調用前,Spring MVC會自動將HTTP請求中的參數(shù)轉換成方法參數(shù)2023-04-04使用Idea或Datagrip導入excel數(shù)據(jù)的方法
這篇文章主要介紹了使用Idea或Datagrip導入excel數(shù)據(jù)的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11FeignClient支持運行時動態(tài)指定URL方式
在實際開發(fā)中,我們經(jīng)常通過FeignClient接口調用三方API,當面對不同的環(huán)境對應不同的地址時,可以通過配置文件和占位符來切換,但在同一個環(huán)境中需要調用不同地址的相同接口時,這種方法就失效了,此時,可以通過實現(xiàn)RequestInterceptor接口來動態(tài)切換地址2024-11-11