Procházet zdrojové kódy

新增token拦截器,前后端无感刷新token

xusl před 2 roky
rodič
revize
9dbf1ebfd5

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/annotation/InterfaceLimit.java

@@ -12,7 +12,7 @@ import java.lang.annotation.*;
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface InterfaceLimit {
-    long time() default 1000; // 限制时间 单位:毫秒(默认值:一分钟)
+    long time() default 1000; // 限制时间 单位:毫秒
 
     int value() default 1; // 允许请求的次数(默认值:5次)
 }

+ 21 - 0
backend/src/main/java/com/jiayue/ssi/config/WebConfig.java

@@ -0,0 +1,21 @@
+package com.jiayue.ssi.config;
+
+import com.jiayue.ssi.interceptor.TokenStatusInterceptor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+*
+*
+* @author xsl
+* @since 2023/03/03
+*/
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry){
+        registry.addInterceptor(new TokenStatusInterceptor()).addPathPatterns("/**")
+                .excludePathPatterns("/refreshToken","/error","/getMailCode","/getVerifyCode","/login","/index.html","/user/login","/css/**","/images/**","/js/**","/fonts/**");
+    }
+}

+ 18 - 4
backend/src/main/java/com/jiayue/ssi/controller/UserLoginController.java

@@ -4,10 +4,7 @@ import com.jiayue.ssi.annotation.InterfaceLimit;
 import com.jiayue.ssi.constant.CacheConstants;
 import com.jiayue.ssi.entity.SysUser;
 import com.jiayue.ssi.service.SysUserService;
-import com.jiayue.ssi.util.IdUtils;
-import com.jiayue.ssi.util.LocalCache;
-import com.jiayue.ssi.util.RandomUtil;
-import com.jiayue.ssi.util.ResponseVO;
+import com.jiayue.ssi.util.*;
 import com.wf.captcha.SpecCaptcha;
 import com.wf.captcha.base.Captcha;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,6 +36,8 @@ public class UserLoginController {
     JavaMailSender javaMailSender;
     @Value("${spring.mail.username}")
     String fromMailAddress;
+    @Autowired
+    JwtTokenUtil jwtTokenUtil;
 
     /**
      * 生成验证码
@@ -118,4 +117,19 @@ public class UserLoginController {
         }
         return ResponseVO.success();
     }
+
+    /**
+     * 刷新token
+     *
+     * @param httpServletRequest
+     * @param httpServletResponse
+     * @throws IOException
+     */
+    @PostMapping("/refreshToken")
+    public ResponseVO refreshToken(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
+            throws Exception {
+        String oldToken = httpServletRequest.getHeader("Authorization");
+        String newToken = jwtTokenUtil.refreshToken(oldToken);
+        return ResponseVO.success(newToken);
+    }
 }

+ 1 - 1
backend/src/main/java/com/jiayue/ssi/filter/InterfaceLimitFilter.java

@@ -26,7 +26,7 @@ public class InterfaceLimitFilter extends OncePerRequestFilter {
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
         throws ServletException, IOException {
-        if (!InterfaceLimitUtil.checkInterface(request, 1000, 1)) {
+        if (!InterfaceLimitUtil.checkInterface(request, 1000, 5)) {
             log.error("接口拦截:{} 请求超过限制频率【{}次/{}ms】,IP为{}", request.getRequestURI(), 1000, 1, request.getRemoteAddr());
             response.setHeader("Access-Control-Allow-Origin", "*");
             response.setStatus(401);

+ 0 - 5
backend/src/main/java/com/jiayue/ssi/filter/VerifySmFilter.java

@@ -37,7 +37,6 @@ public class VerifySmFilter extends OncePerRequestFilter {
             if (StringUtils.isNotEmpty(tokenStr)){
                 // 解密token
                 String decryptTokenStr = SM2CryptUtils.decrypt(tokenStr,SecretKeyConstants.SERVER_PRIVATE_KEY);
-                System.out.println("接收token后解密:"+decryptTokenStr);
                 String tokenSign = request.getHeader("TokenSign");
                 // 验证签名
                 boolean verifySign = SM2CryptUtils.verifySign(SecretKeyConstants.CLIENT_PUBLIC_KEY,decryptTokenStr,tokenSign);
@@ -59,7 +58,6 @@ public class VerifySmFilter extends OncePerRequestFilter {
             // 验证加密的参数文本
             String data_sm2 = request.getParameter("secretData");
             if (StringUtils.isNotEmpty(data_sm2)){
-                System.out.println("接收前端加密:"+data_sm2);
                 try {
                     decryptStr = SM2CryptUtils.decrypt(data_sm2, SecretKeyConstants.SERVER_PRIVATE_KEY);
                 }
@@ -68,7 +66,6 @@ public class VerifySmFilter extends OncePerRequestFilter {
                     ResponseInfo.doResponse(response,"参数解密失败,不能访问系统!",401);
                     return;
                 }
-                System.out.println("解密后:" + decryptStr);
                 // 验签前端参数
                 String paramSign = request.getParameter("paramSign");
                 try {
@@ -105,7 +102,6 @@ public class VerifySmFilter extends OncePerRequestFilter {
                     tempMap.put(fieldStr[0],fieldStr[1]);
                 }
                 // 对加密串解密验签
-                System.out.println("接收get请求secretData:"+tempMap.get("secretData"));
                 try {
                     decryptStr = SM2CryptUtils.decrypt(tempMap.get("secretData"), SecretKeyConstants.SERVER_PRIVATE_KEY);
                 }
@@ -114,7 +110,6 @@ public class VerifySmFilter extends OncePerRequestFilter {
                     ResponseInfo.doResponse(response,"参数解密失败,不能访问系统!",401);
                     return;
                 }
-                System.out.println("解密后:" + decryptStr);
                 // 验签前端参数
                 String paramSign = tempMap.get("paramSign");
                 // 验证签名

+ 39 - 0
backend/src/main/java/com/jiayue/ssi/interceptor/TokenStatusInterceptor.java

@@ -0,0 +1,39 @@
+package com.jiayue.ssi.interceptor;
+
+import com.jiayue.ssi.util.JwtTokenUtil;
+import com.jiayue.ssi.util.ResponseInfo;
+import io.jsonwebtoken.Claims;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* 提前判断token是否需要刷新
+*
+* @author xsl
+* @since 2023/03/07
+*/
+public class TokenStatusInterceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {
+        // 统一拦截
+        String token = request.getHeader("Authorization");
+        JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
+        Claims claims = jwtTokenUtil.getClaimsFromToken(token);
+        // 获取失效时间
+        long expirationTime = claims.getExpiration().getTime();
+        if (System.currentTimeMillis()>(expirationTime-(1000*60*4+40000))){
+            // 刷新token
+            ResponseInfo.doResponse(response,"token即将失效,需要刷新!",409);
+            return false;
+        }
+        return true;
+    }
+}

+ 14 - 6
backend/src/main/java/com/jiayue/ssi/util/JwtTokenUtil.java

@@ -20,12 +20,12 @@ import java.util.Map;
 @Component
 public class JwtTokenUtil {
 
-    private String secret = "ssqq";
+    private String secret = "jiayue";
 
     /**
-     * 过期时长
+     * 过期时长(毫秒)
      */
-    private Long expiration = 630720000000L;
+    private Long expiration = 300000L;
 
     private String authorization;
 
@@ -46,7 +46,7 @@ public class JwtTokenUtil {
      * @param token 令牌
      * @return 数据声明
      */
-    private Claims getClaimsFromToken(String token) {
+    public Claims getClaimsFromToken(String token) {
         Claims claims;
         try {
             claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
@@ -149,7 +149,15 @@ public class JwtTokenUtil {
     }
 
     public static void main(String[] args) {
-        String token="eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MjMwODg3NjM3NywiaWF0IjoxNjc4MTU2Mzc3NzU5fQ.imVgkdOog0YVVzhlS-mIFfvz9FyKsdKzjT21YhU2zs1o35scTs3qhyy7kX8ZtXJEJKvdRR5SuWN07DCi-57eew";
-        System.out.println(new JwtTokenUtil().getUsernameFromToken(token));
+        JwtTokenUtil jwtTokenUtil = new JwtTokenUtil();
+        Map<String, Object> claims = new HashMap<String, Object>(16);
+        claims.put(Claims.SUBJECT, "Test");
+        claims.put(Claims.ISSUED_AT, new Date());
+        String token=jwtTokenUtil.generateToken(claims);
+        System.out.println(jwtTokenUtil.getUsernameFromToken(token));
+        Claims claims1 = jwtTokenUtil.getClaimsFromToken(token);
+        System.out.println("过期时间:"+claims1.getExpiration());
+        System.out.println("过期时间提前2分钟:"+new Date(claims1.getExpiration().getTime()-1000*60*2));
+
     }
 }

+ 1 - 3
ui/src/layout/components/Navbar.vue

@@ -42,11 +42,9 @@
       this.$store.dispatch('app/toggleSideBar')
     },
     async logout() {
-      const tok = getBrowserToken();
-      document.cookie = 'token='+tok+';expires=' + new Date(0).toUTCString()
+      sessionStorage.removeItem("token")
       //注销返回自己的登录页
       this.$router.push(`/login?redirect=${this.$route.fullPath}`)
-      sessionStorage.clear()
     }
   }
 }

+ 58 - 25
ui/src/main.js

@@ -60,6 +60,7 @@ let publicKey2 = '041967638ca43d4577d8dba166bff4437fde944270101f398a95b846ec2f81
 let privateKey1 = '27ce6eec39dbf3b564a77c4da1e129fe1ba01a92f6d61055a33ed14ffcbc949e'
 
 Vue.prototype.$axios.interceptors.request.use(
+
   config => {
     // get请求映射params参数
     if (config.method === 'get' && config.params) {
@@ -85,7 +86,6 @@ Vue.prototype.$axios.interceptors.request.use(
     }
 
     if (sessionStorage.getItem("token")!=="undefined" && sessionStorage.getItem("token")!==undefined && sessionStorage.getItem("token")!=null) { // 判断是否存在token,如果存在的话,则每个http header都加上token
-      alert('存在')
       let tokenStr = doEncrypt(sessionStorage.getItem("token"))
       config.headers['Authorization'] = tokenStr
       config.headers['TokenSign'] = doSign(sessionStorage.getItem("token"))
@@ -94,31 +94,19 @@ Vue.prototype.$axios.interceptors.request.use(
   },
   error => {
     // do something with request error
-    console.log(error) // for debug
+    // console.log(error) // for debug
     return Promise.reject(error)
   }
 )
 
-function getBrowserUser() {
-  var user = "";
-  var ca = document.cookie.split(';');
-  for (var i = 0; i < ca.length; i++) {
-    var c = ca[i].trim();
-    if (c.indexOf("user=") == 0) {
-      user = c.substring("user=".length, c.length);
-    }
-  }
-  return user
-}
+// 是否正在刷新的标记
+let isRefreshing = false
+//重试队列
+let requests = []
 
 // response interceptor
 Vue.prototype.$axios.interceptors.response.use(
   /**
-   * If you want to get http information such as headers or status
-   * Please return  response => response
-   */
-
-  /**
    * Determine the request status by custom code
    * Here is just an example
    * You can also judge the status by HTTP Status Code
@@ -139,11 +127,12 @@ Vue.prototype.$axios.interceptors.response.use(
       // if the custom code is not 20000, it is judged as an error.
       //console.log(res.code)
       if (data.code !== 0) {
-        Message({
-          message: data.message || 'Error',
-          type: 'error',
-          duration: 5 * 1000
-        })
+        // alert('准备error输出')
+        // Message({
+        //   message: data.message || 'Error',
+        //   type: 'error',
+        //   duration: 5 * 1000
+        // })
 
         // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
         if (data.code === 50008 || data.code === 50012 || data.code === 50014) {
@@ -163,7 +152,7 @@ Vue.prototype.$axios.interceptors.response.use(
         return data
       }
   },
-  error => {
+  async error => {
     if (error.response) {
       switch (error.response.status) {
         case 401:
@@ -203,15 +192,59 @@ Vue.prototype.$axios.interceptors.response.use(
           console.log('服务器关闭了!')
           resetRouter()
           break
+        case 409:
+            if (!isRefreshing) {
+              isRefreshing = true
+              //调用刷新token的接口
+              return await  Vue.prototype.$axios.post(
+                  '/refreshToken'
+                ).then((res) => {
+                  // const { token } = res.data
+                  // 替换token
+                  sessionStorage.setItem('token', res.data)
+                  // Vue.prototype.$axios(error.response.config)
+                  // 重新请求接口 前过期的接口
+                  error.config.headers.Authorization = res.data;
+                  requests.length > 0 && requests.map((cb) => {
+                    cb();
+                  });
+                  requests = [];  //注意要清空
+                  ///////////////////////////////问媛媛////////////////////
+                error.config.url='/sysUserController/getAll'
+                  return Vue.prototype.$axios.request(error.config);
+                  // return error.response.config
+                }).catch(err => {
+                  console.log(err)
+                  //跳到登录页
+                  removeToken()
+                  router.push('/login')
+                  // return Promise.reject(err)
+                }).finally(() => {
+                  isRefreshing = false
+                })
+            }
+            else {
+              // 正在刷新token ,把后来的接口缓冲起来
+              return new Promise((resolve) => {
+                requests.push(() => {
+                  error.config.headers.Authorization = sessionStorage.getItem('token');
+                  resolve(Vue.prototype.$axios.request(error.config));
+                });
+              })
+            }
+            break
+          // return Promise.reject(error.response.data)
       }
     }
+    else{
+      return Promise.reject(error)
+    }
     /*    console.log('err' + error) // for debug
     Message({
       message: error.message,
       type: 'error',
       duration: 5 * 1000
     })*/
-    return Promise.reject(error)
   }
 )
 

+ 1 - 2
ui/src/utils/auth.js

@@ -11,6 +11,5 @@ export function setToken(token) {
 }
 
 export function removeToken() {
-  alert('移除token')
-  return Cookies.remove(TokenKey)
+  sessionStorage.removeItem('token')
 }

+ 1 - 0
ui/src/views/sysManager/userManager/index.vue

@@ -313,6 +313,7 @@
           }
           this.loading = false
         }).catch((error) => {
+          this.loading = false
           this.$message.error('获取用户信息出错' + error)
         })
       },