|
@@ -0,0 +1,125 @@
|
|
|
|
+package com.caimei365.user.config;
|
|
|
|
+
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
+import com.caimei365.user.components.RedisService;
|
|
|
|
+import com.caimei365.user.utils.JwtUtil;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.apache.commons.lang.StringUtils;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.core.io.buffer.DataBuffer;
|
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
|
+import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
|
|
+import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
+import org.springframework.web.server.ServerWebExchange;
|
|
|
|
+import org.springframework.web.server.WebFilter;
|
|
|
|
+import org.springframework.web.server.WebFilterChain;
|
|
|
|
+import reactor.core.publisher.Flux;
|
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
|
+
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
+import java.util.Arrays;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * JWT Token
|
|
|
|
+ *
|
|
|
|
+ * 续签逻辑:
|
|
|
|
+ * 登录成功后,用户在未过期时间内继续操作,续签token。
|
|
|
|
+ * 登录成功后,空闲超过过期时间,返回token已失效,重新登录。
|
|
|
|
+ * 实现逻辑:
|
|
|
|
+ * 1.登录成功后将token存储到redis里面(这时候k、v值一样都为token),并设置过期时间为token过期时间
|
|
|
|
+ * 2.当用户请求时token值还未过期,则重新设置redis里token的过期时间。
|
|
|
|
+ * 3.当用户请求时token值已过期,但redis中还在,则JWT重新生成token并覆盖v值(这时候k、v值不一样了),然后设置redis过期时间。
|
|
|
|
+ * 4.当用户请求时token值已过期,并且redis中也不存在,则用户空闲超时,返回token已失效,重新登录。
|
|
|
|
+ *
|
|
|
|
+ * @author : Charles
|
|
|
|
+ * @date : 2021/3/30
|
|
|
|
+ */
|
|
|
|
+@Slf4j
|
|
|
|
+@Component
|
|
|
|
+public class JWTFilter implements WebFilter {
|
|
|
|
+
|
|
|
|
+ private static final String AUTH = "X-Token";
|
|
|
|
+ /**
|
|
|
|
+ * 需要权限认证的接口路径
|
|
|
|
+ */
|
|
|
|
+ private static final String[] PERMISSION_URLS = new String[]{
|
|
|
|
+ "/user/club/info",
|
|
|
|
+ "/user/club/info/update",
|
|
|
|
+ "/user/shop/info",
|
|
|
|
+ "/user/shop/info/update"
|
|
|
|
+ };
|
|
|
|
+ private RedisService redisService;
|
|
|
|
+ @Autowired
|
|
|
|
+ public void setRedisService(RedisService redisService) {
|
|
|
|
+ this.redisService = redisService;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
|
|
|
+ ServerHttpRequest request = exchange.getRequest();
|
|
|
|
+ ServerHttpResponse response = exchange.getResponse();
|
|
|
|
+
|
|
|
|
+ HttpHeaders header = request.getHeaders();
|
|
|
|
+ String token = header.getFirst(AUTH);
|
|
|
|
+ String url = request.getURI().getPath();
|
|
|
|
+
|
|
|
|
+ if (StringUtils.isBlank(token) && Arrays.asList(PERMISSION_URLS).contains(url)) {
|
|
|
|
+ log.error("未经授权,Token为空!");
|
|
|
|
+ return tokenErrorResponse(response, "未经授权,Token为空!");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (StringUtils.isNotBlank(token)) {
|
|
|
|
+ String cacheToken = (String) redisService.get(token);
|
|
|
|
+ // token续签
|
|
|
|
+ if (StringUtils.isNotBlank(cacheToken) && !"null".equals(cacheToken) && JwtUtil.isVerify(cacheToken)) {
|
|
|
|
+ int userId = JwtUtil.parseTokenUid(cacheToken);
|
|
|
|
+ log.error("Token续签,UserId:"+userId+",Token:"+token);
|
|
|
|
+ // 再次校验token有效性
|
|
|
|
+ if (!JwtUtil.isVerify(cacheToken)) {
|
|
|
|
+ // 生成token
|
|
|
|
+ String newToken = JwtUtil.createToken(userId);
|
|
|
|
+ // 将token存入redis,并设置超时时间(秒)
|
|
|
|
+ redisService.set(token, newToken, JwtUtil.getExpireTime());
|
|
|
|
+ } else {
|
|
|
|
+ // 重新设置超时时间(秒)
|
|
|
|
+ redisService.expire(token, JwtUtil.getExpireTime());
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 需要验证的路径
|
|
|
|
+ if(Arrays.asList(PERMISSION_URLS).contains(url)) {
|
|
|
|
+ // Token失效
|
|
|
|
+ log.error("Token失效,请重新登录!");
|
|
|
|
+ return tokenErrorResponse(response, "Token失效,请重新登录!");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // //TODO 将用户信息存放在请求header中传递给下游业务
|
|
|
|
+ // ServerHttpRequest.Builder mutate = request.mutate();
|
|
|
|
+ // mutate.header("demo-user-name", username);
|
|
|
|
+ // ServerHttpRequest buildReuqest = mutate.build();
|
|
|
|
+ //
|
|
|
|
+ // //todo 如果响应中需要放数据,也可以放在response的header中
|
|
|
|
+ // response.setStatusCode(HttpStatus.OK);
|
|
|
|
+ // response.getHeaders().add("demo-user-name",username);
|
|
|
|
+ // return chain.filter(exchange.mutate()
|
|
|
|
+ // .request(buildReuqest)
|
|
|
|
+ // .response(response)
|
|
|
|
+ // .build());
|
|
|
|
+ return chain.filter(exchange);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Mono<Void> tokenErrorResponse(ServerHttpResponse response, String responseMsg){
|
|
|
|
+ response.setStatusCode(HttpStatus.OK);
|
|
|
|
+ response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
|
|
|
+ JSONObject res = new JSONObject();
|
|
|
|
+ res.put("code", -99);
|
|
|
|
+ res.put("msg", responseMsg);
|
|
|
|
+ byte[] responseByte = res.toJSONString().getBytes(StandardCharsets.UTF_8);
|
|
|
|
+ DataBuffer buffer = response.bufferFactory().wrap(responseByte);
|
|
|
|
+ return response.writeWith(Flux.just(buffer));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|