JWT详解

Lou.Chen
大约 5 分钟

1、有状态和无状态登录

有状态:

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如Tomcat中的Session。例如登录:用户登录后,我们把用户的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session,然后下次请求,用户携带cookie值来(这一步有浏览器自动完成),我们就能识别到对应session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,不支持集群化部署
无状态:

微服务集群中的每个服务,对外提供的都使用RESTful风格的接口。而RESTful风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

那么这种无状态性有哪些好处呢?

  • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器

  • 服务端的集群和状态对客户端透明

  • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)

  • 减小服务端存储压力

1.1 如何实现无状态登录

无状态登录的流程:

  • 首先客户端发送账户名/密码到服务端进行认证

  • 认证通过后,服务端将用户信息加密并且编码成一个token,返回给客户端

  • 以后客户端每次发送请求,都需要携带认证的token

  • 服务端对客户端发送来的token进行解密,判断是否有效,并且获取用户登录信息

2、JWT

https://jwt.io/introductionopen in new window

2.1 简介

JSON Web Token简称JWT

2.2 认证流程

2.3 JWT结构

JWT包含三部分数据:

  • Header:标头通常由两部分组成:令牌的类型(即JwT)和所使用的签名算法,例如 HMAC SHA256或RSA。它会使用Base64编码(可解码)组成JWT结构的第一部分。这里头部的内容一般不做修改

    注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

    • typ声明类型,这里是JWT
    • alg加密算法,自定义
    {
        "alg":"HS256",
        "typ":"JWT"
    }
    
  • Payload:令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分

    不要放一些敏感的信息,例如用户的密码

    {"authorities":"ROLE_user,","sub":"zs","exp":1585668423}
    
  • Signature:签名,是整个数据的认证信息。用于验证整个数据完整和可靠性。前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。 Signature需要使用编码后的 header和 payload以及我们提供的一个密钥,然后使用 header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过

    例如:签名:HMACSHA256(base64urlencode(header)+"."+base64urlencode(payload), secret);

    • 签名的目的:签名目的最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的內容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的.如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

示例格式Header.Payload.Signature

eyJhbGciOiJIUzUxMiJ9.

  • {"alg":"HS512"}
    

eyJhdXRob3JpdGllcyI6IlJPTEVfdXNlciwiLCJzdWIiOiJ6cyIsImV4cCI6MTU4NTY2ODQyM30.

  • {"authorities":"ROLE_user,","sub":"zs","exp":1585668423}
    

5IgsesOWhkVPpyLbIHWbgeNNQFZvIRik2FlZHWCIoD7jH1b2BOgYHqWKb7LehZ_lyK53ZggXIdLVQJ_cqnVPtQ

  • 最后一段无法解密

2.4 JWT token动态刷新

https://www.zhihu.com/question/309815576/answer/579255324open in new window

实现目标:

  • 延长token过期时间
  • 活跃用户在token过期时,在用户无感知的情况下动态刷新token,做到一直在线状态
  • 不活跃用户在token过期时,直接定向到登录页

动态刷新token:

前端检测到token过期后,携带refreshJwt访问后台刷新token的接口,服务端在拦截器中依然对refreshJwt进行解析鉴权

  • 假如refreshJwt也过期了,提示登录过期,强制跳转登录页
  • 假如refreshJwt还在有效期,则签发新的token返回,前端使用最新的token进行接口请求
  • 如果refreshJwt 过期则直接提示需要重新登录

总结:

  • 如果是活跃用户,那么允许他在refreshJwt过期时间与token过期时间的差值这段时间内,不停的动态刷新token,使其做到无感知的状态下一直保持登录状态
  • 如果用户不活跃,在refreshJwt过期时间到了,依然没有使用系统,那么将判定为不活跃用户,此时应当重定向到登录页了

token验证策略:

可以用拦截器做到针对一些方法调用用户认证,也可以考虑自定义注解来标识一些方法,过滤器也可以达到相同效果。我使用jwt时会使用redis存储设置有效期,生成的token同样在redis中存储,登录时验证是否在redis中存在,如果存在则说明token有效,一般来说在登出时将redis中对应的token删除,这样保证不会别人拿到token就可以随意登录,哪怕token在jwt生成的有效期内。至于你说的不同业务的过期时间不一样,这个要用token来做吗?至于你说的“一段时间”的情况,应该根据具体业务,可能会重新生成token并返回来保证会话,也可能直接退出重新登录

3、项目整合示例

详情请参考本目录下的 jwt_test 项目