16| 分布式应用下登录检验解决方案 JWT 小滴课堂讲师 2024年08月05日 预计阅读 12 分钟 原文 JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。**通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息** 优点 - `生产的token可以包含基本信息`,比如id、用户昵称、头像等信息,避免再次查库 - 存储在客户端,`不占用服务端的内存资源` 缺点 - token是经过base64编码,所以`可以解码`,因此token加密前的对象`不应该包含敏感信息`,如用户权限,密码等 - 如果没有服务端存储,则`不能做登录失效处理`,除非服务端改秘钥 **JWT格式组成 头部、负载、签名** - header+payload+signature - 头部:主要是描述`签名算法` - 负载:主要描述是`加密对象的信息`,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户 - 签名:主要是把前面`两部分进行加密`,防止别人拿到token进行base解密后篡改token - 关于jwt客户端存储 - **可以存储在cookie,localstorage和sessionStorage里面** **开发JWT工具类** common项目加入相关依赖 ```java io.jsonwebtoken jjwt 0.7.0 javax.xml.bind jaxb-api 2.3.1 ``` common项目中封装生产token方法,校验token方法 ```java @Data public class LoginUser { // 主键 private Long id; // 名称 private String name; // 头像 @JsonProperty("head_img") private String headImg; // 邮箱 private String mail; } ``` ```java @Slf4j public class JWTUtil { /** * token 过期时间,正常是7天,方便测试我们改为70 */ private static final long EXPIRE = 1000 * 60 * 60 * 24 * 7 * 10; /** * 加密的秘钥 */ private static final String SECRET = "yuan.net666"; /** * 令牌前缀 */ private static final String TOKEN_PREFIX = "yuan1024shop"; /** * subject */ private static final String SUBJECT = "yuan"; /** * 根据用户信息,生成令牌 */ public static String geneJsonWebToken(LoginUser loginUser) { if (loginUser == null) { throw new NullPointerException("loginUser对象为空"); } String token = Jwts.builder().setSubject(SUBJECT) //payload .claim("head_img", loginUser.getHeadImg()) .claim("id", loginUser.getId()) .claim("name", loginUser.getName()) .claim("mail", loginUser.getMail()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET).compact(); token = TOKEN_PREFIX + token; return token; } /** * 校验token的方法 */ public static Claims checkJWT(String token) { try { final Claims claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody(); return claims; } catch (Exception e) { log.info("jwt token解密失败"); return null; } } } ``` 登录整合到UserServiceImpl的登录中生成token: ```java LoginUser loginUser = new LoginUser(); BeanUtils.copyProperties(userDO,loginUser); String accessToken = JWTUtil.geneJsonWebToken(loginUser); return JsonData.buildSuccess(accessToken); ``` #### JWT登录过期-自动刷新token 在前后分离场景下,越来越多的项目使用jwt token作为接口的安全机制,但存在jwt过期后,用户无法直接感知,假如在用户操作页面期间,突然提示登录,则体验很不友好,所以就有了token自动刷新需求 但是这个自动刷新方案,基本都离不开服务端状态存储,JWT推出思想是:去中心化,无状态化,所以有所违背 类似这样的业务,有阿里云首页,没有做token刷新令牌维护,但是符合对应的思想。 1. **方案:前端控制检测token,无感知刷新** 用户登录成功的时候,一次性给他两个Token,分别为**AccessToken和RefreshToken** - AccessToken有效期较短,比如1天或者5天,用于正常请求 - RefreshToken有效期可以设置长一些,例如10天、20天,作为刷新AccessToken的凭证 刷新方案:**当AccessToken即将过期的时候,例如提前30分钟,客户端利用RefreshToken请求指定的API获取新的AccessToken并更新本地存储中的AccessToken** **核心逻辑** - 登录成功后,jwt生成AccessToken; UUID生成RefreshToken并存储在服务端redis中,设置过期时间 - 接口返回3个字段AccessToken/RefreshToken/访问令牌过期时间戳 - 由于RefreshToken存储在服务端redis中,假如这个RefreshToken也过期,则提示重新登录; > 疑问:RefreshToken有效期那么长,和直接将AccessToken的有效期延长有什么区别? > > RefreshToken不像AccessToken那样在大多数请求中都被使用,主要是本地检测accessToken快过期的时候才使用,一般本地存储的时候,也不叫refreshToken,前端可以取个别名,混淆代码让攻击者不能直接识别这个就是刷新令牌 - 缺点:前端每次请求需要判断token距离过期时间 - 优点:后端压力小,代码逻辑改动不大 2. **方案二:后端存储判断过期时间** 后端存储AccessToken,每次请求过来都判断是否要过期,如果快要过期则重新生成新的token,并返回给前端重新存储,比如距离1天就过期的情况,如果用户访问对应的接口则会更新,但假如没访问则token已经过期则需要重新登录 - 优点:前端改动小,只需要存储响应http头里面是否有新的令牌产生,有的话就重新存储。 - 缺点:后端实现复杂,且泄露后容易存在一直保活状态,且前端会存在并发请求,当并发请求收到多个jwt token时,容易生成多个token混乱使用 ```java //刷新token的方案 @PostMapping("refresh_token") public JsonData getRefreshToken(Map param){ //先去redis,找refresh_token是否存在 //refresh_token存在,解密accessToken //重新调用JWTUtil.geneJsonWebToken() 生成accessToken //重新生成refresh_token,并存储redis,设置30天过期时间 //返回给前端 return null; } ``` #### JWT令牌token泄露恶意使用-解决方案 **解密**:使用互联网大厂的产品时经常遇到这个情况 - **比如阿里云或者淘宝,你现在登录了然后换个网络或者地域就需要重新登录** - 就是`对应的token令牌,不只简单的算法加密,还包括了客户端属性、地理网络位置信息等`,一起组成一个token令牌 如何避免token令牌泄露被恶意使用 - 生成token的时候,加密的payload`加入当前用户ip`。拦截器解密后,获取payload的ip和当前访问ip判断是否同个,如果不是则提示重新登录。 - 优点:服务端无需存储相关内容,性能高,假如用户广州登录,泄露了token给杭州的黑客,依旧用不了 - 缺点:如果用户用使用过程中`ip变动频繁,则操作会经常提示重新登录,体验不友好` - 当然也`可以让用户开启安全模式和非安全模式`,让用户自己知道这个情况,一些区块链、比特币交易所里面就会`让用户自己选择控制这个token令牌安全是否和ip、终端、地理网络信息进行绑定`。
评论区