https://jwt.io/
header(头部)承载两部分信息:
playload(载荷)就是存放有效信息的地方,这些有效信息包含三个部分:
signature(签证信息)
header+.+payload
进行base64编码,然后实用签证算法和密钥加密不足之处:
JWT保存在客户端
都保存了用户身份信息,都有过期时间。session翻译为会话,token翻译为令牌。session是空间换时间,token是时间换空间。
JWT保存在客户端,在分布式环境下不需要做额外工作。而session因为保存在服务端,分布式环境下需要实现多机数据共享
session一般需要结合COOKIE实现认证,所以需要浏览器支持COOKIE,因此移动端无法使用session认证方案
安全性:JWT的payload使用的是base64编码的,因此在JWT中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。
性能:经过编码之后JWT将非常长,COOKIE的限制大小一般是4k,COOKIE很可能放不下,所以JWT一般放在local storage里面。并且用户在系统中的每一次http请求都会把JWT携带在Header里面,HTTP请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用JWT的HTTP请求比使用session的开销大得多
一次性:无状态是JWT的特点,但也导致了这个问题,JWT是一次性的。想修改里面的内容,就必须签发一个新的JWT,若想废弃,一种常用的处理手段是结合redis或者续约
Acesss Token是访问资源接口(API)时所需要的资源凭证。简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign。每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里,基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库token 完全由应用管理,所以它可以避开同源策略
特点:
token 的身份验证流程:
refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
相同:
区别:
<dependency><groupId>com.auth0groupId><artifactId>java-jwtartifactId><version>3.18.1version>
dependency>
public class JWTUtil {private static final Logger logger &#61; LoggerFactory.getLogger(JWTUtil.class);//私钥private static final String TOKEN_SECRET &#61; "123456";/*** 生成token&#xff0c;自定义过期时间 毫秒*/public static String generateToken(UserTokenDTO userTokenDTO) {try {// 私钥和加密算法Algorithm algorithm &#61; Algorithm.HMAC256(TOKEN_SECRET);// 设置头部信息Map<String, Object> header &#61; 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;}}/*** 检验token是否正确*/public static UserTokenDTO parseToken(String token) {Algorithm algorithm &#61; Algorithm.HMAC256(TOKEN_SECRET);JWTVerifier verifier &#61; JWT.require(algorithm).build();DecodedJWT jwt &#61; verifier.verify(token);String tokenInfo &#61; jwt.getClaim("token").asString();return JSON.parseObject(tokenInfo, UserTokenDTO.class);}
}
public final class RedisServiceImpl implements RedisService {/*** 过期时长*/private final Long DURATION &#61; 1 * 24 * 60 * 60 * 1000L;&#64;Resourceprivate RedisTemplate redisTemplate;private ValueOperations<String, String> valueOperations;&#64;PostConstructpublic void init() {RedisSerializer redisSerializer &#61; new StringRedisSerializer();redisTemplate.setKeySerializer(redisSerializer);redisTemplate.setValueSerializer(redisSerializer);redisTemplate.setHashKeySerializer(redisSerializer);redisTemplate.setHashValueSerializer(redisSerializer);valueOperations &#61; redisTemplate.opsForValue();}&#64;Overridepublic void set(String key, String value) {valueOperations.set(key, value, DURATION, TimeUnit.MILLISECONDS);log.info("key&#61;{}, value is: {} into redis cache", key, value);}&#64;Overridepublic String get(String key) {String redisValue &#61; valueOperations.get(key);log.info("get from redis, value is: {}", redisValue);return redisValue;}&#64;Overridepublic boolean delete(String key) {boolean result &#61; redisTemplate.delete(key);log.info("delete from redis, key is: {}", key);return result;}&#64;Overridepublic Long getExpireTime(String key) {return valueOperations.getOperations().getExpire(key);}
}
public String login(LoginUserVO loginUserVO) {//1.判断用户名密码是否正确UserPO userPO &#61; userMapper.getByUsername(loginUserVO.getUsername());if (userPO &#61;&#61; null) {throw new UserException(ErrorCodeEnum.TNP1001001);}if (!loginUserVO.getPassword().equals(userPO.getPassword())) {throw new UserException(ErrorCodeEnum.TNP1001002);}//2.用户名密码正确生成tokenUserTokenDTO userTokenDTO &#61; new UserTokenDTO();PropertiesUtil.copyProperties(userTokenDTO, loginUserVO);userTokenDTO.setId(userPO.getId());userTokenDTO.setGmtCreate(System.currentTimeMillis());String token &#61; JWTUtil.generateToken(userTokenDTO);//3.存入token至redisredisService.set(userPO.getId(), token);return token;
}
public boolean loginOut(String id) {boolean result &#61; redisService.delete(id);if (!redisService.delete(id)) {throw new UserException(ErrorCodeEnum.TNP1001003);}return result;
}
public String updatePassword(UpdatePasswordUserVO updatePasswordUserVO) {// 1.修改密码UserPO userPO &#61; UserPO.builder().password(updatePasswordUserVO.getPassword()).id(updatePasswordUserVO.getId()).build();UserPO user &#61; userMapper.getById(updatePasswordUserVO.getId());if (user &#61;&#61; null) {throw new UserException(ErrorCodeEnum.TNP1001001);}if (userMapper.updatePassword(userPO) !&#61; 1) {throw new UserException(ErrorCodeEnum.TNP1001005);}// 2.生成新的tokenUserTokenDTO userTokenDTO &#61; UserTokenDTO.builder().id(updatePasswordUserVO.getId()).username(user.getUsername()).gmtCreate(System.currentTimeMillis()).build();String token &#61; JWTUtil.generateToken(userTokenDTO);// 3.更新tokenredisService.set(user.getId(), token);return token;
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {String authToken &#61; request.getHeader("Authorization");String token &#61; authToken.substring("Bearer".length() &#43; 1).trim();UserTokenDTO userTokenDTO &#61; JWTUtil.parseToken(token);//1.判断请求是否有效if (redisService.get(userTokenDTO.getId()) &#61;&#61; null || !redisService.get(userTokenDTO.getId()).equals(token)) {return false;}//2.判断是否需要续期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;
}
&#64;Configuration
public class InterceptorConfig implements WebMvcConfigurer {&#64;Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(authenticateInterceptor()).excludePathPatterns("/logout/**") //开发的路径.excludePathPatterns("/login/**") //开发的路径.addPathPatterns("/**");}&#64;Beanpublic AuthenticateInterceptor authenticateInterceptor() {return new AuthenticateInterceptor();}
}
可以参考&#xff1a;https://juejin.cn/post/6962142423879270437
单点登录将token或者一个唯一标识UUID&#61;UUID.randomUUID().toString()
存进COOKIE中&#xff08;别存在Http的header中了&#xff09;&#xff0c;设置路径为整个项目根路径/*
&#xff1b; 往往以这个唯一标识为key&#xff0c;用户信息为value缓存在服务器中&#xff01;&#xff01;&#xff01;