springboot集成JWT之雙重token的實現(xiàn)
一,單個token缺點
token一般存儲在瀏覽器中,容易被盜取,而為了防止被盜取長期使用,token的有效時間必然無法不能設置太長,此舉也必然引起用戶的頻繁登錄,給用戶帶來不好的體驗
二,雙重token(accessToken,refreshToken)
(一)設計思路
1,將accessToken作為是否登錄的標識,存儲在瀏覽器當中。由于該token可瀏覽器看到,容易被盜取,可將有效時間設置的盡可能短一些,解決盜取長期使用問題
2,將refreshToken作為是否更新accessToken的標識,只要refreshToken不過期,則自動更新accessToken,可將有效時間設置的長些,解決頻繁登錄問題
3,不直接將refreshToken存儲到瀏覽器上,而是將其存儲到accessToken的載荷里,后端通過獲取accessToken的載荷內(nèi)容獲取到refreshToken,防止refreshToken被盜取
4,accessToken構(gòu)成:
載荷:refreshToken
有效時間:盡可能短(這里我設置為30分鐘)
密鑰:用戶的密碼
refreshToken構(gòu)成:
載荷:用戶的id
有效時間:長(這里我設置為一天)
密鑰:用戶的密碼
(二)后端代碼
1,TokenUtil(生成token和獲取用戶信息)
具體步驟看注釋
@Component
@Slf4j
public class TokenUtils {
private static IUserService staticAdminService;
@Resource
private IUserService adminService;
@PostConstruct
public void setUserService() {
staticAdminService = adminService;
}
// 生成token
public static String genToken(String adminId, String sign, Integer time) {
//生成過期時間
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, time*60);
return JWT.create().withAudience(adminId) //載荷
.withExpiresAt(instance.getTime()) //time分鐘后過期
.sign(Algorithm.HMAC256(sign)); // 密鑰
}
//根據(jù)accessToken獲取用戶信息
// 1.通過accessToken的載荷拿到refreshToken
// 2.通過refreshToken的載荷拿到userId
// 3.調(diào)用根據(jù)用戶id獲取用戶信息的方法拿到用戶信息
public static User getCurrentAdmin() {
String accessToken = null;
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
accessToken = request.getHeader("token");
System.out.println("access"+accessToken);
if (StrUtil.isBlank(accessToken)) {
log.error("獲取當前登錄的accessToken失敗, token: {}", accessToken);
return null;
}
String refreshToken = JWT.decode(accessToken).getAudience().get(0);
if (StrUtil.isBlank(refreshToken)) {
log.error("獲取當前登錄的refreshToken失敗, token: {}", refreshToken);
return null;
}
String userId = JWT.decode(refreshToken).getAudience().get(0);
return staticAdminService.getById(Integer.valueOf(userId));
} catch (Exception e) {
log.error("獲取當前登錄的管理員信息失敗, token={}", accessToken, e);
return null;
}
}
}
2,JwtInterceptor(檢驗accessToken是否合法)
實現(xiàn)思路:
(1)檢驗accessToken是否為空
(2)通過accessToken的載荷內(nèi)容拿到refreshToken
(3)驗證獲取到的refreshToken是否合法,若不合法,則accessToken為無效token,合法則返回密鑰
(4)通過該密鑰去判斷accessToken是否過期
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private IUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String accessToken = request.getHeader("token");
if (StrUtil.isBlank(accessToken)) {
accessToken = request.getParameter("token");
}
//執(zhí)行認證
if (StrUtil.isBlank(accessToken)) {
throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg());
}
String refreshToken;
try{
refreshToken = JWT.decode(accessToken).getAudience().get(0);
}catch (Exception e){
throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
}
String password = verifyRefreshToken(refreshToken);
try {
// 用戶密碼加簽驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build();
jwtVerifier.verify(accessToken); // 驗證token
} catch (JWTVerificationException e) {
throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
}
return true;
}
//驗證refreshToken是否合法
public String verifyRefreshToken(String token){
// 獲取 token 中的adminId
String adminId;
User user;
try {
adminId = JWT.decode(token).getAudience().get(0);
// 根據(jù)token中的userid查詢數(shù)據(jù)庫
user = userService.getById(Integer.parseInt(adminId));
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
}
if (user == null) {
throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg());
}
try {
// 用戶密碼加簽驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
jwtVerifier.verify(token); // 驗證token
} catch (JWTVerificationException e) {
throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
}
return user.getPassword();
}
}
3, WebConfig(設置攔截規(guī)則)
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 加自定義攔截器JwtInterceptor,設置攔截規(guī)則
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/refreshToken/refresh");
}
@Bean
public JwtInterceptor jwtInterceptor(){
return new JwtInterceptor();
}
}
4,登陸接口處生成accessToken和refreshToken
String refreshToken= TokenUtils.genToken(user.getId().toString(),user.getPassword(),24*60); String accessToken=TokenUtils.genToken(refreshToken,user.getPassword(),30);
5,編寫更新refreshToken的接口
實現(xiàn)思路:
(1)檢驗accessToken是否為空
(2)通過accessToken的載荷內(nèi)容拿到refreshToken
(3)驗證獲取到的refreshToken是否合法,不合法,則accessToken為無效token(此處無效token單指自己編寫,不是后端生成的token),不對accessToken進行更新操作,合法則重新生成新的accessToken
@RestController
@RequestMapping("/refreshToken")
public class refreshTokenController {
@Autowired
private IUserService userService;
@GetMapping("/refresh")
public Result refresh(@RequestParam String accessToken) {
if (StrUtil.isBlank(accessToken)) {
throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg());
}
String refreshToken;
try{
refreshToken = JWT.decode(accessToken).getAudience().get(0);
}catch (Exception e){
throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg());
}
if(StrUtil.isBlank(refreshToken)){
return Result.error(ErrorCode.REFRESH_TOKEN_NULL.getCode(), ErrorCode.REFRESH_TOKEN_NULL.getMsg());
}
// 獲取 token 中的adminId
String adminId;
User user;
try {
adminId = JWT.decode(refreshToken).getAudience().get(0);
// 根據(jù)token中的userid查詢數(shù)據(jù)庫
user = userService.getById(Integer.parseInt(adminId));
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
}
if (user == null) {
throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg());
}
try {
// 用戶密碼加簽驗證 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
jwtVerifier.verify(refreshToken); // 驗證token
} catch (JWTVerificationException e) {
throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg());
}
String currentAccessToken= TokenUtils.genToken(refreshToken,user.getPassword(),30);
return Result.success(currentAccessToken);
}
}
(三)前端代碼
1,登錄成功后存儲accessToken
存儲方式不限,在這我是存儲在localStorage里,并在vuex里共享該數(shù)據(jù)
//storage.js
const TOKEN_KEY = 'tk'
export const getInfo = () => {
const token = localStorage.getItem(TOKEN_KEY)
return token || ''
}
export const setInfo = (token) => {
localStorage.setItem(TOKEN_KEY, token)
}
export const removeInfo = () => {
localStorage.removeItem(TOKEN_KEY)
}
//token模塊
import { getInfo, setInfo ,removeInfo} from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
tokenInfo: getInfo()
}
},
mutations: {
setTokenInfo (state, info) {
state.tokenInfo = info
setInfo(state.tokenInfo)
},
removeTokenInfo () {
removeInfo()
}
},
actions: {}
}
store.commit('token/setTokenInfo', data)
2,axios請求攔截器處設置請求頭
instance.interceptors.request.use(function (config) {
const accessToken = store.state.token.tokenInfo
if (accessToken) {
config.headers.token = accessToken
}
return config
}, function (error) {
// 對請求錯誤做些什么
return Promise.reject(error)
})
3,axios響應攔截器配置
設置accessToken過期則判斷refreshToken是否過期,如果未過期,重新生成accessToken,并重新發(fā)送請求
instance.interceptors.response.use(async function (response) {
if (response.data.code === 200) {
return response.data
} else if (response.data.code === 1008 || response.data.code === 1009 || response.data.code === 1010) {
//accessToken過期
const { data } = await refreshToken(store.state.token.tokenInfo)
store.commit('token/setTokenInfo', data)
return instance(response.config)
} else if (response.data.code === 1014) {
//refreshToken過期
window.location.href = '/login'
} else {
Vue.prototype.$message.error(response.data.msg)
return Promise.reject(response.data.msg)
}
}, function (error) {
// 對響應錯誤做點什么
return Promise.reject(error)
})
4,守衛(wèi)路由配置
判斷是否有accessToken,是否在登錄注冊頁面,若都無則跳轉(zhuǎn)到登錄頁,重新登錄
router.beforeEach(async (to, from, next) => {
const token = store.state.token.tokenInfo
if (
// 檢查用戶是否已登錄
!token &&
// ?? 避免無限重定向
to.path !== '/login' && to.path !== '/register'
) {
// 將用戶重定向到登錄頁面
next({ path: '/login' })
} else {
next()
}
})到此這篇關于springboot集成JWT之雙重token的實現(xiàn)的文章就介紹到這了,更多相關springboot 雙重token內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot?使用?Sa-Token?完成權限認證的操作方法
- SpringBoot項目引入token設置方式
- SpringBoot如何集成Token
- SpringBoot基于Redis實現(xiàn)token的在線續(xù)期的實踐
- springboot+vue實現(xiàn)Token自動續(xù)期(雙Token方案)
- SpringBoot中Token登錄授權、續(xù)期和主動終止的方案流程分析
- SpringBoot權限認證-Sa-Token的使用詳解
- 使用SpringBoot簡單實現(xiàn)無感知的刷新 Token功能
- springBoot整合jwt實現(xiàn)token令牌認證的示例代碼
- springboot中通過jwt令牌校驗及前端token請求頭進行登錄攔截實戰(zhàn)記錄
- springboot實現(xiàn)token驗證登陸狀態(tài)的示例代碼
- SpringBoot+Vue中的Token續(xù)簽機制
相關文章
Java中json使用方法_動力節(jié)點Java學院整理
JSON(JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式, json是個非常重要的數(shù)據(jù)結(jié)構(gòu),在web開發(fā)中應用十分廣泛。下面通過本文給大家講解Java中json使用方法,感興趣的朋友一起看看吧2017-07-07
Mybatis 如何批量刪除數(shù)據(jù)的實現(xiàn)示例
這篇文章主要介紹了Mybatis 如何批量刪除數(shù)據(jù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03
Java對象和Json文本轉(zhuǎn)換工具類的實現(xiàn)
Json?是一個用于Java對象和Json文本相互轉(zhuǎn)換的工具類,本文主要介紹了Java對象和Json文本轉(zhuǎn)換工具類,具有一定的參考價值,感興趣的可以了解一下2022-03-03

