当前位置:网站首页>token过期自动续费方案和实现
token过期自动续费方案和实现
2022-07-02 05:51:00 【想吃凤梨酥】
解决方案
方案1: 每一次请求都进行重新生成一个新的token【频率过高,性能不好】
方案2: 每次登录的时候生成两个token给前端进行返回,一个是用于鉴别用户身份的token,另外一个token则是用于刷新token用的
方案3: 登录过后给前端进行返回token并设置了过期时间30分钟,每次请求的时候前端把token存在请求头里面进行发请求,后端接收请求的时候获取请求头出来进行jwt解析判断过期时间是否小于10分钟,如果小于10分钟就生成新的token在responseHearde进行返回即可
方案二实现
实现过程:
- 登录成功以后,后端返回 access_token 和 refresh_token,客户端缓存此两种token;
- 使用 access_token 请求接口资源,成功则调用成功;如果token超时,客户端携带 refresh_token 调用token刷新接口获取新的 access_token;
- 后端接受刷新token的请求后,检查 refresh_token 是否过期。如果过期,拒绝刷新,客户端收到该状态后,跳转到登录页;如果未过期,生成新的 access_token 返回给客户端。
- 客户端携带新的 access_token 重新调用上面的资源接口。
- 客户端退出登录或修改密码后,注销旧的token,使 access_token 和 refresh_token 失效,同时清空客户端的 access_token 和 refresh_toke。
后端
LoginController
控制器里
package com.liu.token.controller;
import com.liu.token.domain.Login;
import com.liu.token.utlis.JwtUtil;
import com.liu.token.utlis.ResponseResult;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@RestController
@RequestMapping("/user")
public class LoginController {
@PostMapping("/login")
public ResponseResult login(@RequestBody Login login){
// 验证凭证,1分钟
String access_token = JwtUtil.createJWT(login.getUsername(), 60*1000l);
// 验证凭证过期使用这个来刷新凭证,30天
String refresh_token = JwtUtil.createJWT(login.getUsername(), 30*24*60*60*1000l);
HashMap<String,String> map=new HashMap<>();
map.put("access_token",access_token);
map.put("refresh_token",refresh_token);
return new ResponseResult(200,"登录成功!",map);
}
// 验证路由,不重要,只是提供访问一下
@PostMapping("/logout")
public ResponseResult logout(){
HashMap<String,Object> map=new HashMap<>();
map.put("access_token",123456);
map.put("refresh_token",123456);
return new ResponseResult(200,"llalaallaal",map);
}
}
LoginFilter
过滤器里
package com.liu.token.filter;
import com.alibaba.fastjson.JSON;
import com.liu.token.common.CustomException;
import com.liu.token.utlis.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@Slf4j
@WebFilter
public class LoginFilter implements Filter {
// 路径匹配器
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}", requestURI);
// 定义不需要处理的请求路径
String[] urls = new String[]{
"/user/login"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if (check) {
log.info("本次请求{}不需要处理", requestURI);
filterChain.doFilter(request, response);
return;
}
String access_token = request.getHeader("access_token");
String refresh_token = request.getHeader("refresh_token");
if (access_token==null||refresh_token==null){
throw new CustomException("没有登录,请登录!");
}
HashMap<String,Object> map = new HashMap();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try {
// 过期了会报错,就会执行catch代码块
Claims claims = JwtUtil.parseJWT(access_token);
} catch (Exception e) {
try {
Claims claims = JwtUtil.parseJWT(refresh_token);
System.out.println(claims);
String id = claims.get("sub").toString();
String jwt = JwtUtil.createJWT(id, 60 * 1000l);
map.put("code",401);
map.put("msg","刷新token");
map.put("data",jwt);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
} catch (Exception ex) {
map.put("code",402);
map.put("msg","token已经过期,请重新登录!");
map.put("data",null);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
}
}
filterChain.doFilter(request, response);
}
/** * 路径匹配,检查本次请求是否需要放行 * * @param urls * @param requestURL * @return */
public boolean check(String[] urls, String requestURL) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURL);
if (match) {
return true;
}
}
return false;
}
}
前端
login.vue
登录测试页面
<template>
<div class="home">
<input type="text" v-model="username">
<input type="text" v-model="password">
<button @click="login">登录</button>
</div>
<div>
<button @click="logout">测试</button>
</div>
</template>
<script> import request from "@/config/request" export default {
data() {
return {
username:"123456", password:"123456", } }, methods: {
login() {
request.post("/user/login", {
username: this.username, password:this.password }).then(res => {
let {
code, msg, data } = res; console.log(data); if (code == 200) {
// 保存token window.localStorage.setItem("access_token",data.access_token) window.localStorage.setItem("refresh_token",data.refresh_token) } }) }, logout() {
request.post("/user/logout", {
username: this.username, password:this.password }).then(res => {
let {
code, msg, data } = res; console.log(res); }) } } } </script>
request.js
//导入axios 和 qs 插件
import axios from "axios"
import qs from "qs"
//配置全局公共域名
// const baseURL = process.env.NODE_ENV === "production" ? "https://www.xxxxx.com" : "";
//创建axios实例
let request = axios.create({
baseURL: "/api", //赋值公共域名
timeout: 5000 //设置延迟时间(单位:毫秒)
})
//拦截request的发送请求和响应请求,并做一定的配置
request.interceptors.request.use(
//拦截发送请求,并给请求头信息headers加上token令牌
config => {
config.headers["access_token"] = localStorage.getItem("access_token");
config.headers["refresh_token"] = localStorage.getItem("refresh_token");
return config
},
err => {
Promise.reject(err)
}
)
request.interceptors.response.use(
//拦截响应请求 , 这里直接返回数据
response => {
console.log(response)
// console.log(response.headers)
if (response.data.code == 200) {
return response.data;
} else if (response.data.code == 401) {
console.log("--------------------------------")
window.localStorage.setItem("access_token", response.data.data);
return request.request(response.config);
} else if (response.data.code == 402) {
alert("请重新登录!跳转路由逻辑")
}
},
err => {
Promise.reject(err)
}
)
//导出request
export default request;
方案三实现
后端
LoginController
控制器里
package com.liu.token.controller;
import com.liu.token.domain.Login;
import com.liu.token.utlis.JwtUtil;
import com.liu.token.utlis.ResponseResult;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public ResponseResult login(@RequestBody Login login){
// 验证凭证,11分钟
String access_token = JwtUtil.createJWT(login.getUsername(), 30*60*1000l);
HashMap<String,String> map=new HashMap<>();
map.put("access_token",access_token);
return new ResponseResult(200,"登录成功!",map);
}
// 验证路由,不重要,只是提供访问一下
@PostMapping("/logout")
public ResponseResult logout(){
HashMap<String,Object> map=new HashMap<>();
map.put("access_token",123456);
map.put("refresh_token",123456);
return new ResponseResult(200,"llalaallaal",map);
}
}
LoginFilter
过滤器里
package com.liu.token.filter;
import com.alibaba.fastjson.JSON;
import com.liu.token.common.CustomException;
import com.liu.token.utlis.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@Slf4j
@WebFilter
public class LoginFilter implements Filter {
// 路径匹配器
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}", requestURI);
// 定义不需要处理的请求路径
String[] urls = new String[]{
"/user/login"
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if (check) {
log.info("本次请求{}不需要处理", requestURI);
filterChain.doFilter(request, response);
return;
}
String access_token = request.getHeader("access_token");
if (access_token==null){
throw new CustomException("没有登录,请登录!");
}
try {
Claims claims = JwtUtil.parseJWT(access_token);
int exp = (int)claims.get("exp");
String id = claims.get("sub").toString();
long minute = (exp*1000l - System.currentTimeMillis())/1000/60;
// 还有10分钟过期时就更新token
if (minute<10){
String token = JwtUtil.createJWT(id, 30 * 60 * 1000l);
response.addHeader("token",token);
}
} catch (Exception e) {
HashMap<String,Object> map = new HashMap();
map.put("code",402);
map.put("msg","token已过期,重新登录!");
map.put("data",null);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
}
filterChain.doFilter(request, response);
}
/** * 路径匹配,检查本次请求是否需要放行 * * @param urls * @param requestURL * @return */
public boolean check(String[] urls, String requestURL) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURL);
if (match) {
return true;
}
}
return false;
}
}
前端
login.vue
登录测试页面
<template>
<div class="home">
<input type="text" v-model="username">
<input type="text" v-model="password">
<button @click="login">登录</button>
</div>
<div>
<button @click="logout">测试</button>
</div>
</template>
<script> import request from "@/config/request" export default {
data() {
return {
username:"123456", password:"123456", } }, methods: {
login() {
request.post("/user/login", {
username: this.username, password:this.password }).then(res => {
let {
code, msg, data } = res; console.log(data); if (code == 200) {
window.localStorage.setItem("access_token",data.access_token) } }) }, logout() {
request.post("/user/logout", {
username: this.username, password:this.password }).then(res => {
let {
code, msg, data } = res; console.log(res); }) } } } </script>
request.js
//导入axios 和 qs 插件
import axios from "axios"
import qs from "qs"
//配置全局公共域名
// const baseURL = process.env.NODE_ENV === "production" ? "https://www.xxxxx.com" : "";
//创建axios实例
let request = axios.create({
baseURL: "/api", //赋值公共域名
timeout: 5000 //设置延迟时间(单位:毫秒)
})
//拦截request的发送请求和响应请求,并做一定的配置
request.interceptors.request.use(
//拦截发送请求,并给请求头信息headers加上token令牌
config => {
config.headers["access_token"] = localStorage.getItem("access_token");
return config
},
err => {
Promise.reject(err)
}
)
request.interceptors.response.use(
//拦截响应请求 , 这里直接返回数据
response => {
console.log(response)
if (response.data.code == 200) {
// 如果响应头部有token就存起来
let token = response.headers["token"];
if (token) {
window.localStorage.setItem("access_token", token);
}
return response.data;
} else if (response.data.code == 402) {
alert("请重新登录!跳转路由逻辑")
}
},
err => {
Promise.reject(err)
}
)
//导出request
export default request;
边栏推荐
- 【LeetCode】Day92-盛最多水的容器
- 1035 Password
- Get the details of the next largest number
- mysql的约束总结
- 金融门户相关信息
- 如何写出好代码 — 防御式编程指南
- brew install * 失败,解决方法
- Software testing learning - day 4
- Conglin environmental protection rushes to the scientific and Technological Innovation Board: it plans to raise 2billion yuan, with an annual profit of more than 200million yuan
- 来啦~ 使用 EasyExcel 导出时进行数据转换系列新篇章!
猜你喜欢
Installation du tutoriel MySQL 8.0.22 par centos8
How to write good code - Defensive Programming Guide
ThreadLocal memory leak
Importation de studio visuel
Pytorch Basics
Thunder on the ground! Another domestic 5g chip comes out: surpass Huawei and lead the world in performance?
Record sentry's path of stepping on the pit
Huawei Hongmeng OS, is it OK?
Brew install * failed, solution
idea开发工具常用的插件合集汇总
随机推荐
Thunder on the ground! Another domestic 5g chip comes out: surpass Huawei and lead the world in performance?
PHP read file (read JSON file, convert to array)
Conglin environmental protection rushes to the scientific and Technological Innovation Board: it plans to raise 2billion yuan, with an annual profit of more than 200million yuan
来啦~ 使用 EasyExcel 导出时进行数据转换系列新篇章!
Innovation never stops -- the innovation process of nvisual network visualization platform for Excel import
中小型项目手撸过滤器实现认证与授权
460. LFU cache bidirectional linked list
mysql事务和隔离级别
Zzuli:1065 count the number of numeric characters
I want to understand the swift code before I learn it. I understand it
php继承(extends)
线程池概述
“简单”的无限魔方
Get the details of the next largest number
青训营--数据库实操项目
3D 打印机 G 代码命令:完整列表和教程
Lingyunguang rushes to the scientific innovation board: the annual accounts receivable reaches 800million. Dachen and Xiaomi are shareholders
h5跳小程序
“簡單”的無限魔方
JVM class loading mechanism