为了确保用户在整个会话的期间保持登录状态,无需多次认证,我们需要做到无感刷新token
首先当我们用户登录的时候会生成两个token,
Token:存放一些用户非私密信息 ,时间较短 一两个小时即可
RefreshToken: 只存放用户的id ,时间较长 两三个月集或更长
生成的两个token我们需要保存本地存储中,当我们请求的时候携带token进行访问,后台会验证token是否过期,没有过期则正常访问资源,如果过期后台会返回一个状态到前台,前台在拦截器中对这个状态进行判断,如果真的过期了前台就会调用一个更新token的接口并将RefreshToken作为参数携带过去,然后后台对RefreshToken进行校验查看是否是有效令牌,查看完成后会生成新的token 和 RefreshToken返回给前台,然后存储,携带访问即可
前端
// 添加响应拦截器
this.myAxios.interceptors.response.use(
async function (response: any) {
//关闭loading层
closeNotify();
const { code, msg, data } = response.data;
if (code === 0) {
return data;
} else if (code == undefined) {
return response;
} else if (code == 457) {
try {
// 刷新token
await refreshToken();
// 重新执行请求
return that.myAxios(response.config);
} catch(error: any) {
showNotify({type: 'danger',message: error.message})
return Promise.reject(error)
}
} else if (code != 0) {
return Promise.reject(msg);
}
},
function (error: any) {
// 对响应错误做点什么
closeNotify();
return Promise.reject(error);
}
);
}
const refreshToken = async () => {
closeNotify();
return await axios
.get("/apis/bm-member-service-app/api/member/refresh_token", {
params: {
refreshToken: beimao_store.refreshToken,
},
})
.then(function (response) {
const { code, msg, data } = response.data;
if (code != 0) {
return Promise.reject(new Error(msg));
}
beimao_store.token = data.token;
beimao_store.refreshToken = data.refreshToken;
return Promise.resolve("成功");
})
.catch(function (error) {
return Promise.reject(new Error("稍等稍等"));
});
};
后端
/**
* 刷新token
* @param refreshToken
* @return
*/
@GetMapping("/refresh_token")
public Map<String,String> login(String refreshToken) {
return loginService.refresToken(refreshToken);
}
public Map<String, String> refresToken(String refreshToken) {
if(ObjectUtil.isEmpty(refreshToken)){
throw new BizException(456,"token必传");
}
boolean b = false;
// 验证算法,JWTValidator包含过期的验证,验证比较全面
try {
JWTValidator.of(refreshToken).validateAlgorithm(JWTSignerUtil.hs256(key.getBytes())).validateDate();
b = true;
}catch (Exception ex){
ex.printStackTrace();
}
if(!b){
throw new BizException(457,"token不正确");
}
//{id:8,nickName:“张麻子”}
//ThreadLocal 作用 能够跨类跨方法实现变量共享
JSONObject jsonObject = JSONUtil.toBean(JWTUtil.parseToken(refreshToken).getPayload().toString(), JSONObject.class);
int id = Integer.parseInt(jsonObject.get("id").toString());
Member member = loginDao.selectById(id);
if(ObjectUtil.isEmpty(member)){
throw new BizException(610,"账号或密码错误");
}
//设置双Token
//带参数的设置短时间
Map<String , Object> map1 = new HashMap<String,Object>(){
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
put("nickName", member.getNickName());
put("exp", System.currentTimeMillis()/1000 + 10);
}
};
//不带参数的设置长时间
Map<String , Object> map2 = new HashMap<String,Object>(){
private static final long serialVersionUID = 1L;
{
put("id", member.getId());
put("exp", System.currentTimeMillis()/1000 + 1000 * 60 * 60 * 24 * 30 * 2);
}
};
String token = JWTUtil.createToken(map1,key.getBytes());
String refreshToken2 = JWTUtil.createToken(map2,key.getBytes());
//将Token存入map返回出去
Map<String ,String> map = new HashMap<>();
map.put("token", token);
map.put("refreshToken", refreshToken);
return map;
}