feat:实现异步缓存支持

This commit is contained in:
wangyu 2021-12-02 22:31:24 +08:00
parent c127e275c6
commit 097c6210ed
4 changed files with 91 additions and 46 deletions

View File

@ -1,13 +1,11 @@
package com.flyfish.framework.configuration.jwt; package com.flyfish.framework.configuration.jwt;
import com.flyfish.framework.service.MongoUserDetailsService; import com.flyfish.framework.service.MongoUserDetailsService;
import com.flyfish.framework.utils.RedisOperations;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -21,8 +19,6 @@ public class JwtSecurityContextRepository implements ServerSecurityContextReposi
private MongoUserDetailsService userDetailsService; private MongoUserDetailsService userDetailsService;
@Resource @Resource
private TokenProvider tokenProvider; private TokenProvider tokenProvider;
@Resource
private RedisOperations redisOperations;
@Override @Override
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) { public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
@ -30,9 +26,9 @@ public class JwtSecurityContextRepository implements ServerSecurityContextReposi
String jwt = tokenProvider.retrieveToken(serverWebExchange).orElse(null); String jwt = tokenProvider.retrieveToken(serverWebExchange).orElse(null);
URI requestURI = request.getURI(); URI requestURI = request.getURI();
// 存在jwt时校验jwtredis也需要存在 // 存在jwt时校验jwtredis也需要存在
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt) && redisOperations.hasKey(jwt)) { return tokenProvider.validateToken(jwt).flatMap(authorized -> {
if (authorized) {
// token即将过期续租 // token即将过期续租
Authentication authentication = tokenProvider.getAuthentication(jwt); Authentication authentication = tokenProvider.getAuthentication(jwt);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI); log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI);
return userDetailsService.findByUsername(authentication.getName()) return userDetailsService.findByUsername(authentication.getName())
@ -41,6 +37,7 @@ public class JwtSecurityContextRepository implements ServerSecurityContextReposi
log.debug("no valid JWT token found, uri: {}", requestURI); log.debug("no valid JWT token found, uri: {}", requestURI);
return Mono.empty(); return Mono.empty();
} }
});
} }
/** /**

View File

@ -1,7 +1,7 @@
package com.flyfish.framework.configuration.jwt; package com.flyfish.framework.configuration.jwt;
import com.flyfish.framework.domain.base.IUser; import com.flyfish.framework.utils.ReactiveRedisOperations;
import com.flyfish.framework.utils.RedisOperations; import com.flyfish.framework.utils.UUIDUtils;
import io.jsonwebtoken.*; import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException; 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.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.security.Key; import java.security.Key;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@ -37,12 +39,13 @@ public class TokenProvider implements InitializingBean {
public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORITIES_KEY = "auth"; private static final String AUTHORITIES_KEY = "auth";
private static final String TOKEN_BLOCK_PREFIX = "tk_blk_";
private final Boolean remember; private final Boolean remember;
private final long tokenValidityInMilliseconds; private final long tokenValidityInMilliseconds;
private final long tokenValidityInMillisecondsForRememberMe; private final long tokenValidityInMillisecondsForRememberMe;
@Resource @Resource
private RedisOperations redisOperations; private ReactiveRedisOperations reactiveRedisOperations;
@Resource @Resource
private JwtProperties jwtProperties; private JwtProperties jwtProperties;
@ -89,39 +92,50 @@ public class TokenProvider implements InitializingBean {
* @param authentication 认证信息 * @param authentication 认证信息
*/ */
public void addToken(ServerWebExchange exchange, Authentication authentication) { public void addToken(ServerWebExchange exchange, Authentication authentication) {
IUser user = (IUser) authentication.getPrincipal();
String token = createToken(authentication, remember); String token = createToken(authentication, remember);
HttpHeaders headers = exchange.getResponse().getHeaders(); HttpHeaders headers = exchange.getResponse().getHeaders();
// app用户从头部返回方便获取 // app用户从头部返回方便获取
headers.add("Token", token); headers.add("Token", token);
headers.add("Token-Valid-Time", String.valueOf(remember ? tokenValidityInMillisecondsForRememberMe : headers.add("Token-Valid-Time", String.valueOf(remember ? tokenValidityInMillisecondsForRememberMe :
tokenValidityInMilliseconds)); 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<Void> 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) { public String createToken(Authentication authentication, boolean rememberMe) {
String authorities = authentication.getAuthorities().stream() String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority) .map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
long now = (new Date()).getTime(); long now = (new Date()).getTime();
Date validity; long duration = rememberMe ? this.tokenValidityInMillisecondsForRememberMe : this.tokenValidityInMilliseconds;
if (rememberMe) { Date validity = new Date(now + duration);
validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
} else {
validity = new Date(now + this.tokenValidityInMilliseconds);
}
return Jwts.builder() return Jwts.builder()
.setSubject(authentication.getName()) .setSubject(authentication.getName())
.setId(UUIDUtils.generateShortUuid())
.claim(AUTHORITIES_KEY, authorities) .claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512) .signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity) .setExpiration(validity)
@ -135,11 +149,7 @@ public class TokenProvider implements InitializingBean {
* @return 结果 * @return 结果
*/ */
public Authentication getAuthentication(String token) { public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder() Claims claims = parseToken(token);
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities = Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
@ -151,10 +161,31 @@ public class TokenProvider implements InitializingBean {
return new UsernamePasswordAuthenticationToken(principal, token, authorities); 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<Boolean> validateToken(String authToken) {
if (StringUtils.isBlank(authToken)) {
return Mono.just(false);
}
try { try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken); Claims claims = parseToken(authToken);
return true; return reactiveRedisOperations.hasKey(getCacheKey(claims.getId())).map(exists -> !exists);
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature."); log.info("Invalid JWT signature.");
log.trace("Invalid JWT signature trace: {}", e, e); log.trace("Invalid JWT signature trace: {}", e, e);
@ -171,6 +202,6 @@ public class TokenProvider implements InitializingBean {
log.info("Token解析失败"); log.info("Token解析失败");
log.trace("Token解析失败: {}", e, e); log.trace("Token解析失败: {}", e, e);
} }
return false; return Mono.just(false);
} }
} }

View File

@ -30,13 +30,13 @@ public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler {
public Mono<Void> onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { public Mono<Void> onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
ServerHttpResponse response = webFilterExchange.getExchange().getResponse(); ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
tokenProvider.removeToken(webFilterExchange.getExchange()); Mono<Void> removed = tokenProvider.removeToken(webFilterExchange.getExchange());
Object principal = authentication.getPrincipal(); Object principal = authentication.getPrincipal();
if ("anonymous".equals(principal)) { 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()) return removed.then(authenticationAuditor.logout((UserDetails) authentication.getPrincipal())
.then(response.writeWith(authenticationAuditor.transform(Result.ok()))); .then(response.writeWith(authenticationAuditor.transform(Result.ok()))));
} }
} }

View File

@ -139,7 +139,24 @@ public class ReactiveRedisOperations {
*/ */
public Mono<Boolean> set(String key, Object value, long time) { public Mono<Boolean> set(String key, Object value, long time) {
if (time > 0) { 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<Boolean> set(String key, Object value, Duration duration) {
if (duration.getSeconds() > 0) {
return redisTemplate.opsForValue().set(key, value, duration)
.doOnError(Throwable::printStackTrace).onErrorReturn(false); .doOnError(Throwable::printStackTrace).onErrorReturn(false);
} else { } else {
return set(key, value); return set(key, value);