SpringBoot + jwt + vue实现双token下的用户登录状态控制
1.总体概述
用户登录后,后台springboot工程通过jwt技术生成scessToken和freshToken双令牌,并且freshToken的有效时间是scessToken的两倍,如分别设置我半个小时和一个小时,前端vue将scessToken和freshToken存储至token,在业务请求的时候携带scessToken,在scessToken验证失败后,发送刷新token的请求,请求成功会返回新的scessToken和freshToken,前端携带新的scessToken重新发送请求,若失败则退出登录。
2.前端代码实现
vi ~/src/request/index.js
"use strict";
import axios from "axios";
import store from "../store";
// create an axios instance
const request = axios.create({
//baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url
baseURL: "./",
//baseURL: "http://127.0.0.1:8083/",
timeout: 60000 // request timeout
});
// request interceptor
request.interceptors.request.use(
config => {
// Do something before request is sent
console.info("请求拦截");
let activeUser = store.getters.getActiveUser();
if(activeUser !=null){
config.headers["userName"] = store.getters.getActiveUser().userName;
config.headers["accessToken"] = store.getters.getActiveUser().accessToken;
}
return config;
},
error => {
//message.error(error.message)
return Promise.reject(error);
}
);
// response interceptor
request.interceptors.response.use(
response => {
if (response.data.code == "4001") {
return Promise.reject(response);
}else{
return response;
}
},
error => {
return Promise.reject(error);
}
);
request.interceptors.response.use(
response => {
return response;
},
error => {
let response = error;
// console.info(error)
try{
return new Promise((resolve,reject)=>{
store.dispatch("tokenRefresh",store.getters.getActiveUser()).then(() => {
let config = response.config;
let activeUser = store.getters.getActiveUser();
if(activeUser !=null){
config.headers["userName"] = store.getters.getActiveUser().userName;
config.headers["accessToken"] = store.getters.getActiveUser().accessToken;
}
return resolve(axios(config));
}).catch(err=>{
store.dispatch('LogOut').then(()=>{
location.reload()
//this.$router.push({path:'/login'})
});
});
});
}catch(e){
return Promise.reject(error);
}
}
);
export default request;
vi ~/store/index.js
"use strict";
import Vue from "vue";
import Vuex from "vuex";
import cookie from "js-cookie";
import { selectAllUser } from "@/api/user.js";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user:cookie.get("user")
},
getters: {
getUser:(state,getters)=>(userName)=>{
let user = state.userList.filter(item=>{
return (item.userName == userName);
})
if(user.length>0){
return user[0];
}else{
return {};
}
}
},
mutations: {
SET_USER: (state, user) => {
state.user = JSON.stringify(user);
cookie.set("user", JSON.stringify(user));
},
CLEAR_USER: (state) => {
state.user = "";
cookie.set("user", "");
},
},
actions: {
},
Login({ commit }, data) {
return new Promise((resolve, reject) => {
login(data).then(response => {
if (response.data.code == "0000") {
commit("SET_USER", response.data.extend);
resolve();
} else {
reject(response.data.message);
}
}).catch((error)=>{
reject(error.message);
});
});
},
tokenRefresh({commit},data){
return new Promise((resolve, reject) => {
tokenRefresh(data).then(response => {
if (response.data.code == "0000") {
commit("SET_USER", response.data.extend);
resolve();
} else {
reject(response.data.message);
}
}).catch((error)=>{
reject(error.message);
});
});
},
LogOut({ commit }) {
commit("CLEAR_USER");
},
}
});
export default store;
vi ~/api/system.js
//请求后台的方法
import request from "@/request";
//登录
export function login(data) {
return request({
url: "/user/login",
method: "post",
data
});
}
//刷新缓存
export function tokenRefresh(data) {
return request({
url: "/user/tokenRefresh",
method: "post",
data
});
}
3.后端代码实现
3.1 添加jwt的maven依赖
vi pom.xml
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
3.2 java代码
vi com/git/smp/filter/LoginInterceptor.java
package com.git.smp.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.HandlerInterceptor;
import com.git.smp.controller.SmpUserController;
import com.git.smp.utils.JWTUtil;
@Service
public class LoginInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Value("${jwt.password}")
private String jwtPassword;
@Value("${jwt.version}")
private String version;
/*
* @Override public void doFilter(ServletRequest request, ServletResponse
* response, FilterChain chain) throws IOException, ServletException {
* //logger.info(request.getLocalAddr());
*
* HttpServletRequest httpRequest = (HttpServletRequest)request;
* logger.info(httpRequest.getRequestURI());
*
* if("/index.html".equals(httpRequest.getRequestURI())||"/favicon.ico".equals(
* httpRequest.getRequestURI())||"/".equals(httpRequest.getRequestURI())||
* "/user/login".equals(httpRequest.getRequestURI())) { chain.doFilter(request,
* response); return; }
*
* String accessToken = httpRequest.getHeader("accessToken");
*
* if(JWTUtil.verify(accessToken, "smp@ahrcu")) { chain.doFilter(request,
* response); }else { //登录验证失败 //throw new SmpException("401", "令牌无效");
* response.getWriter().write("{\"code\":\"4001\",\"message\":\"\"}"); }
*
* logger.info("登录拦截");
*
* }
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest) request;
logger.info(httpRequest.getRequestURI());
String accessToken = httpRequest.getHeader("accessToken");
if (JWTUtil.verify(accessToken,jwtPassword,version)) {
return true;
} else {
// 登录验证失败 //throw new SmpException("401", "令牌无效");
response.getWriter().write("{\"code\":\"4001\",\"message\":\"\"}");
return false;
}
}
}
vi com/git/smp/controller/system.java
package com.git.smp.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties.Jwt;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.git.smp.core.PageRequest;
import com.git.smp.core.ResponseMessage;
import com.git.smp.dao.UserMapper;
import com.git.smp.dao.UserRoleMapper;
import com.git.smp.entity.User;
import com.git.smp.entity.UserExtend;
import com.git.smp.entity.UserRole;
import com.git.smp.utils.JWTUtil;
import com.git.smp.utils.ObjectUtil;
@RestController
@RequestMapping("/user")
public class SmpUserController {
Logger logger = LoggerFactory.getLogger(SmpUserController.class);
@Autowired
UserMapper UserMapper;
@Autowired
UserRoleMapper UserRoleMapper;
@Value("${jwt.password}")
private String jwtPassword;
@Value("${jwt.version}")
private String version;
@Autowired
private HttpServletRequest request;
/**
*
* @param pageRequest
* @return
*/
@RequestMapping("/login")
public ResponseMessage<UserExtend> login(@RequestBody User user) {
ResponseMessage<UserExtend> responseMessage = new ResponseMessage<UserExtend>();
responseMessage.setCode("0000");
responseMessage.setMessage("查询成功!");
User userRsult = UserMapper.selectByPrimaryKey(user.getUserName());
if (userRsult == null) {
responseMessage.setCode("0001");
responseMessage.setMessage("用户不存在");
}else if(!userRsult.getUserPasswd().equals(user.getUserPasswd())) {
responseMessage.setCode("0001");
responseMessage.setMessage("密码错误");
}else {
String accessToken = JWTUtil.sign(userRsult, 30*60*1000, jwtPassword,version);
String freshToken = JWTUtil.sign(userRsult, 60*60*1000,jwtPassword,version);
UserExtend UserExtend = new UserExtend();
ObjectUtil.copyBeans(userRsult, smpUserExtend);
UserExtend.setAccessToken(accessToken);
UserExtend.setFreshToken(freshToken);
UserExtend.setUserPasswd("");
responseMessage.setExtend(UserExtend);
}
logger.info(responseMessage.getMessage());
return responseMessage;
}
/**
* 刷新token
* @param pageRequest
* @return
*/
@RequestMapping("/tokenRefresh")
public ResponseMessage<UserExtend> login(@RequestBody UserExtend user) {
logger.info("刷新缓存");
ResponseMessage<UserExtend> responseMessage = new ResponseMessage<UserExtend>();
responseMessage.setCode("0000");
responseMessage.setMessage("查询成功!");
if(JWTUtil.verify(user.getFreshToken(), jwtPassword,version)) {
String accessToken = JWTUtil.sign(user, 30*60*1000, jwtPassword,version);
String freshToken = JWTUtil.sign(user, 60*60*1000,jwtPassword,version);
user.setAccessToken(accessToken);
user.setFreshToken(freshToken);
responseMessage.setExtend(user);
}else {
responseMessage.setCode("0001");
responseMessage.setMessage("token已过期");
}
return responseMessage;
}
}
vi com/git/smp/utils.java
package com.git.smp.utils;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.git.smp.entity.SmpUser;
public class JWTUtil {
// 过期时间2分钟
private static final long EXPIRE_TIME = 24*60*60*1000;
/**
* 校验token是否正确
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String secret,String version) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret+version);
JWTVerifier verifier = JWT.require(algorithm).build();
@SuppressWarnings("unused")
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
/**
* 获得token中的信息无需secret解密也能获得
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userName").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min后过期
* @param userName 用户名
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(SmpUser smpUser,long expireTime,String secret,String version) {
try {
Date date = new Date(System.currentTimeMillis()+expireTime);
Algorithm algorithm = Algorithm.HMAC256(secret+version);
// 附带username信息
return JWT.create()
.withClaim("userName", smpUser.getUserName())
.withClaim("userChineseName", smpUser.getUserChineseName())
.withClaim("passWord", smpUser.getUserPasswd())
.withClaim("version", version)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
return null;
}
}
}