JWT 登錄認(rèn)證及 token 自動(dòng)續(xù)期方案解讀
閱讀本文大概需要 6 分鐘。
來(lái)自:juejin.cn/post/6932702419344162823
https://juejin.cn/post/6916150628955717646
技術(shù)選型

區(qū)別
認(rèn)證流程
基于session的認(rèn)證流程
用戶在瀏覽器中輸入用戶名和密碼,服務(wù)器通過(guò)密碼校驗(yàn)后生成一個(gè)session并保存到數(shù)據(jù)庫(kù) 服務(wù)器為用戶生成一個(gè)sessionId,并將具有sesssionId的cookie放置在用戶瀏覽器中,在后續(xù)的請(qǐng)求中都將帶有這個(gè)cookie信息進(jìn)行訪問(wèn) 服務(wù)器獲取cookie,通過(guò)獲取cookie中的sessionId查找數(shù)據(jù)庫(kù)判斷當(dāng)前請(qǐng)求是否有效
基于JWT的認(rèn)證流程
用戶在瀏覽器中輸入用戶名和密碼,服務(wù)器通過(guò)密碼校驗(yàn)后生成一個(gè)token并保存到數(shù)據(jù)庫(kù) 前端獲取到token,存儲(chǔ)到cookie或者local storage中,在后續(xù)的請(qǐng)求中都將帶有這個(gè)token信息進(jìn)行訪問(wèn) 服務(wù)器獲取token值,通過(guò)查找數(shù)據(jù)庫(kù)判斷當(dāng)前token是否有效
優(yōu)缺點(diǎn)
安全性

性能
一次性
無(wú)法廢棄
續(xù)簽
選擇JWT或session
揚(yáng)長(zhǎng)補(bǔ)短,因此在實(shí)際項(xiàng)目中選擇的是使用JWT來(lái)進(jìn)行認(rèn)證
功能實(shí)現(xiàn)
<dependency>
????<groupId>com.auth0groupId>
????<artifactId>java-jwtartifactId>
????<version>3.10.3version>
dependency>
public?class?JWTUtil?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(JWTUtil.class);
????//私鑰
????private?static?final?String?TOKEN_SECRET?=?"123456";
????/**
?????*?生成token,自定義過(guò)期時(shí)間?毫秒
?????*
?????*?@param?userTokenDTO
?????*?@return
?????*/
????public?static?String?generateToken(UserTokenDTO?userTokenDTO)?{
????????try?{
????????????//?私鑰和加密算法
????????????Algorithm?algorithm?=?Algorithm.HMAC256(TOKEN_SECRET);
????????????//?設(shè)置頭部信息
????????????Map?header?=?new?HashMap<>(2);
????????????header.put("Type",?"Jwt");
????????????header.put("alg",?"HS256");
????????????return?JWT.create()
????????????????????.withHeader(header)
????????????????????.withClaim("token",?JSONObject.toJSONString(userTokenDTO))
????????????????????//.withExpiresAt(date)
????????????????????.sign(algorithm);
????????}?catch?(Exception?e)?{
????????????logger.error("generate?token?occur?error,?error?is:{}",?e);
????????????return?null;
????????}
????}
????/**
?????*?檢驗(yàn)token是否正確
?????*
?????*?@param?token
?????*?@return
?????*/
????public?static?UserTokenDTO?parseToken(String?token)?{
????????Algorithm?algorithm?=?Algorithm.HMAC256(TOKEN_SECRET);
????????JWTVerifier?verifier?=?JWT.require(algorithm).build();
????????DecodedJWT?jwt?=?verifier.verify(token);
????????String?tokenInfo?=?jwt.getClaim("token").asString();
????????return?JSON.parseObject(tokenInfo,?UserTokenDTO.class);
????}
}
生成的token中不帶有過(guò)期時(shí)間,token的過(guò)期時(shí)間由redis進(jìn)行管理 UserTokenDTO中不帶有敏感信息,如password字段不會(huì)出現(xiàn)在token中
Redis工具類
public?final?class?RedisServiceImpl?implements?RedisService?{
????/**
?????*?過(guò)期時(shí)長(zhǎng)
?????*/
????private?final?Long?DURATION?=?1?*?24?*?60?*?60?*?1000L;
????@Resource
????private?RedisTemplate?redisTemplate;
????private?ValueOperations?valueOperations;
????@PostConstruct
????public?void?init()?{
????????RedisSerializer?redisSerializer?=?new?StringRedisSerializer();
????????redisTemplate.setKeySerializer(redisSerializer);
????????redisTemplate.setValueSerializer(redisSerializer);
????????redisTemplate.setHashKeySerializer(redisSerializer);
????????redisTemplate.setHashValueSerializer(redisSerializer);
????????valueOperations?=?redisTemplate.opsForValue();
????}
????@Override
????public?void?set(String?key,?String?value)?{
????????valueOperations.set(key,?value,?DURATION,?TimeUnit.MILLISECONDS);
????????log.info("key={},?value?is:?{}?into?redis?cache",?key,?value);
????}
????@Override
????public?String?get(String?key)?{
????????String?redisValue?=?valueOperations.get(key);
????????log.info("get?from?redis,?value?is:?{}",?redisValue);
????????return?redisValue;
????}
????@Override
????public?boolean?delete(String?key)?{
????????boolean?result?=?redisTemplate.delete(key);
????????log.info("delete?from?redis,?key?is:?{}",?key);
????????return?result;
????}
????@Override
????public?Long?getExpireTime(String?key)?{
????????return?valueOperations.getOperations().getExpire(key);
????}
}
業(yè)務(wù)實(shí)現(xiàn)
登陸功能
public?String?login(LoginUserVO?loginUserVO)?{
????//1.判斷用戶名密碼是否正確
????UserPO?userPO?=?userMapper.getByUsername(loginUserVO.getUsername());
????if?(userPO?==?null)?{
????????throw?new?UserException(ErrorCodeEnum.TNP1001001);
????}
????if?(!loginUserVO.getPassword().equals(userPO.getPassword()))?{
????????throw?new?UserException(ErrorCodeEnum.TNP1001002);
????}
????//2.用戶名密碼正確生成token
????UserTokenDTO?userTokenDTO?=?new?UserTokenDTO();
????PropertiesUtil.copyProperties(userTokenDTO,?loginUserVO);
????userTokenDTO.setId(userPO.getId());
????userTokenDTO.setGmtCreate(System.currentTimeMillis());
????String?token?=?JWTUtil.generateToken(userTokenDTO);
????//3.存入token至redis
????redisService.set(userPO.getId(),?token);
????return?token;
}
判斷用戶名密碼是否正確 用戶名密碼正確則生成token 將生成的token保存至redis
登出功能
public?boolean?loginOut(String?id)?{
?????boolean?result?=?redisService.delete(id);
?????if?(!redisService.delete(id))?{
????????throw?new?UserException(ErrorCodeEnum.TNP1001003);
?????}
?????return?result;
}
更新密碼功能
public?String?updatePassword(UpdatePasswordUserVO?updatePasswordUserVO)?{
????//1.修改密碼
????UserPO?userPO?=?UserPO.builder().password(updatePasswordUserVO.getPassword())
????????????.id(updatePasswordUserVO.getId())
????????????.build();
????UserPO?user?=?userMapper.getById(updatePasswordUserVO.getId());
????if?(user?==?null)?{
????????throw?new?UserException(ErrorCodeEnum.TNP1001001);
????}
????if?(userMapper.updatePassword(userPO)?!=?1)?{
????????throw?new?UserException(ErrorCodeEnum.TNP1001005);
????}
????//2.生成新的token
????UserTokenDTO?userTokenDTO?=?UserTokenDTO.builder()
????????????.id(updatePasswordUserVO.getId())
????????????.username(user.getUsername())
????????????.gmtCreate(System.currentTimeMillis()).build();
????String?token?=?JWTUtil.generateToken(userTokenDTO);
????//3.更新token
????redisService.set(user.getId(),?token);
????return?token;
}
更新用戶密碼時(shí)需要重新生成新的token,并將新的token返回給前端,由前端更新保存在local storage中的token,同時(shí)更新存儲(chǔ)在redis中的token,這樣實(shí)現(xiàn)可以避免用戶重新登陸,用戶體驗(yàn)感不至于太差
其他說(shuō)明
攔截器類
public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,
?????????????????????????????Object?handler)?throws?Exception?{
????String?authToken?=?request.getHeader("Authorization");
????String?token?=?authToken.substring("Bearer".length()?+?1).trim();
????UserTokenDTO?userTokenDTO?=?JWTUtil.parseToken(token);
????//1.判斷請(qǐng)求是否有效
????if?(redisService.get(userTokenDTO.getId())?==?null?
????????????||?!redisService.get(userTokenDTO.getId()).equals(token))?{
????????return?false;
????}
????//2.判斷是否需要續(xù)期
????if?(redisService.getExpireTime(userTokenDTO.getId())?1?*?60?*?30)?{
????????redisService.set(userTokenDTO.getId(),?token);
????????log.error("update?token?info,?id?is:{},?user?info?is:{}",?userTokenDTO.getId(),?token);
????}
????return?true;
}
說(shuō)明:
判斷id對(duì)應(yīng)的token是否不存在,不存在則token過(guò)期 若token存在則比較token是否一致,保證同一時(shí)間只有一個(gè)用戶操作
攔截器配置類
@Configuration
public?class?InterceptorConfig?implements?WebMvcConfigurer?{
????@Override
????public?void?addInterceptors(InterceptorRegistry?registry)?{
????????registry.addInterceptor(authenticateInterceptor())
????????????????.excludePathPatterns("/logout/**")
????????????????.excludePathPatterns("/login/**")
????????????????.addPathPatterns("/**");
????}
????@Bean
????public?AuthenticateInterceptor?authenticateInterceptor()?{
????????return?new?AuthenticateInterceptor();
????}
}
推薦閱讀:
SpringBoot+Querydsl 框架,大大簡(jiǎn)化復(fù)雜查詢操作
數(shù)據(jù)庫(kù)連接池設(shè)置多大才合適?
內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬(wàn)并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
?戳閱讀原文領(lǐng)取!? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱?
評(píng)論
圖片
表情

