From 097c6210ed3f6944ce9a8f7ee98960b36ec279b6 Mon Sep 17 00:00:00 2001 From: wangyu <727842003@qq.com> Date: Thu, 2 Dec 2021 22:31:24 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AE=9E=E7=8E=B0=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E7=BC=93=E5=AD=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/JwtSecurityContextRepository.java | 27 +++--- .../configuration/jwt/TokenProvider.java | 83 +++++++++++++------ .../handler/JsonLogoutSuccessHandler.java | 8 +- .../utils/ReactiveRedisOperations.java | 19 ++++- 4 files changed, 91 insertions(+), 46 deletions(-) diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java index 1e02849..185971b 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java @@ -1,13 +1,11 @@ package com.flyfish.framework.configuration.jwt; import com.flyfish.framework.service.MongoUserDetailsService; -import com.flyfish.framework.utils.RedisOperations; import lombok.extern.slf4j.Slf4j; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.web.server.context.ServerSecurityContextRepository; -import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -21,8 +19,6 @@ public class JwtSecurityContextRepository implements ServerSecurityContextReposi private MongoUserDetailsService userDetailsService; @Resource private TokenProvider tokenProvider; - @Resource - private RedisOperations redisOperations; @Override public Mono load(ServerWebExchange serverWebExchange) { @@ -30,17 +26,18 @@ public class JwtSecurityContextRepository implements ServerSecurityContextReposi String jwt = tokenProvider.retrieveToken(serverWebExchange).orElse(null); URI requestURI = request.getURI(); // 存在jwt时,校验jwt。redis也需要存在 - if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt) && redisOperations.hasKey(jwt)) { - // token即将过期,续租 - - Authentication authentication = tokenProvider.getAuthentication(jwt); - log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI); - return userDetailsService.findByUsername(authentication.getName()) - .flatMap(userDetails -> userDetailsService.loadContext(userDetails)); - } else { - log.debug("no valid JWT token found, uri: {}", requestURI); - return Mono.empty(); - } + return tokenProvider.validateToken(jwt).flatMap(authorized -> { + if (authorized) { + // token即将过期,续租 + Authentication authentication = tokenProvider.getAuthentication(jwt); + log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI); + return userDetailsService.findByUsername(authentication.getName()) + .flatMap(userDetails -> userDetailsService.loadContext(userDetails)); + } else { + log.debug("no valid JWT token found, uri: {}", requestURI); + return Mono.empty(); + } + }); } /** diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java index 4fefcfa..cbad364 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java @@ -1,7 +1,7 @@ package com.flyfish.framework.configuration.jwt; -import com.flyfish.framework.domain.base.IUser; -import com.flyfish.framework.utils.RedisOperations; +import com.flyfish.framework.utils.ReactiveRedisOperations; +import com.flyfish.framework.utils.UUIDUtils; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.DecodingException; @@ -18,9 +18,11 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.security.Key; +import java.time.Duration; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -37,12 +39,13 @@ public class TokenProvider implements InitializingBean { public static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORITIES_KEY = "auth"; + private static final String TOKEN_BLOCK_PREFIX = "tk_blk_"; private final Boolean remember; private final long tokenValidityInMilliseconds; private final long tokenValidityInMillisecondsForRememberMe; @Resource - private RedisOperations redisOperations; + private ReactiveRedisOperations reactiveRedisOperations; @Resource private JwtProperties jwtProperties; @@ -89,39 +92,50 @@ public class TokenProvider implements InitializingBean { * @param authentication 认证信息 */ public void addToken(ServerWebExchange exchange, Authentication authentication) { - IUser user = (IUser) authentication.getPrincipal(); String token = createToken(authentication, remember); HttpHeaders headers = exchange.getResponse().getHeaders(); // app用户从头部返回,方便获取 headers.add("Token", token); headers.add("Token-Valid-Time", String.valueOf(remember ? tokenValidityInMillisecondsForRememberMe : tokenValidityInMilliseconds)); - // token在web端的时间较短,不允许记住,所以使用短期 -// exchange.getResponse().addCookie(ResponseCookie.from(AUTHORIZATION_HEADER, "Bearer-" + token). -// httpOnly(true).maxAge(tokenValidityInMilliseconds).build()); - // redis存储时间长 - redisOperations.set(token, true, tokenValidityInMillisecondsForRememberMe); } - public void removeToken(ServerWebExchange exchange) { - retrieveToken(exchange).ifPresent(token -> redisOperations.del(token)); + /** + * 移除token + * + * @param exchange 交换报文 + * @return 结果 + */ + public Mono removeToken(ServerWebExchange exchange) { + return Mono.justOrEmpty(retrieveToken(exchange)) + .map(this::parseToken) + .flatMap(token -> { + long distance = token.getExpiration().getTime() - System.currentTimeMillis(); + String key = getCacheKey(token.getId()); + return reactiveRedisOperations.set(key, true, Duration.ofMillis(distance)).then(); + }) + .onErrorResume(e -> Mono.empty()); } + /** + * 创建token + * + * @param authentication 认证信息 + * @param rememberMe 记住我标记 + * @return 结果 + */ public String createToken(Authentication authentication, boolean rememberMe) { String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); - Date validity; - if (rememberMe) { - validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); - } else { - validity = new Date(now + this.tokenValidityInMilliseconds); - } + long duration = rememberMe ? this.tokenValidityInMillisecondsForRememberMe : this.tokenValidityInMilliseconds; + Date validity = new Date(now + duration); return Jwts.builder() .setSubject(authentication.getName()) + .setId(UUIDUtils.generateShortUuid()) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(validity) @@ -135,11 +149,7 @@ public class TokenProvider implements InitializingBean { * @return 结果 */ public Authentication getAuthentication(String token) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); + Claims claims = parseToken(token); Collection authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) @@ -151,10 +161,31 @@ public class TokenProvider implements InitializingBean { return new UsernamePasswordAuthenticationToken(principal, token, authorities); } - public boolean validateToken(String authToken) { + /** + * 解析token + * + * @param token 用户token、 + * @return 结果 + */ + public Claims parseToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private String getCacheKey(String jti) { + return TOKEN_BLOCK_PREFIX + jti; + } + + public Mono validateToken(String authToken) { + if (StringUtils.isBlank(authToken)) { + return Mono.just(false); + } try { - Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken); - return true; + Claims claims = parseToken(authToken); + return reactiveRedisOperations.hasKey(getCacheKey(claims.getId())).map(exists -> !exists); } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("Invalid JWT signature."); log.trace("Invalid JWT signature trace: {}", e, e); @@ -171,6 +202,6 @@ public class TokenProvider implements InitializingBean { log.info("Token解析失败!"); log.trace("Token解析失败: {}", e, e); } - return false; + return Mono.just(false); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java index 4c57f04..4da92e4 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java @@ -30,13 +30,13 @@ public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler { public Mono onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); - tokenProvider.removeToken(webFilterExchange.getExchange()); + Mono removed = tokenProvider.removeToken(webFilterExchange.getExchange()); Object principal = authentication.getPrincipal(); if ("anonymous".equals(principal)) { - return response.writeWith(authenticationAuditor.transform(Result.ok())); + return removed.then(response.writeWith(authenticationAuditor.transform(Result.ok()))); } // 记录日志 - return authenticationAuditor.logout((UserDetails) authentication.getPrincipal()) - .then(response.writeWith(authenticationAuditor.transform(Result.ok()))); + return removed.then(authenticationAuditor.logout((UserDetails) authentication.getPrincipal()) + .then(response.writeWith(authenticationAuditor.transform(Result.ok())))); } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/ReactiveRedisOperations.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/ReactiveRedisOperations.java index 35dea57..fc94ce8 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/utils/ReactiveRedisOperations.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/ReactiveRedisOperations.java @@ -139,7 +139,24 @@ public class ReactiveRedisOperations { */ public Mono set(String key, Object value, long time) { if (time > 0) { - return redisTemplate.opsForValue().set(key, value, Duration.of(time, ChronoUnit.SECONDS)) + return redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(time)) + .doOnError(Throwable::printStackTrace).onErrorReturn(false); + } else { + return set(key, value); + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param duration 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public Mono set(String key, Object value, Duration duration) { + if (duration.getSeconds() > 0) { + return redisTemplate.opsForValue().set(key, value, duration) .doOnError(Throwable::printStackTrace).onErrorReturn(false); } else { return set(key, value);