0%

基于jwt的token认证

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案

1. token认证

当用户成功登陆系统并成功验证有效之后,服务器会利用某种机制产生一个token字符串,这个token中可以包含很多信息,例如来源IP,过期时间,用户信息等, 把这个字符串下发给客户端,客户端在之后的每次请求中都携带着这个token,携带方式其实很自由,无论是cookie方式还是其他方式都可以。当服务端收到请求,取出token进行验证(可以验证来源ip,过期时间等信息),如果合法则允许进行操作。

1.1 优点

  1. 支持跨域访问:Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输。
  2. 无状态: Token机制在服务端不需要存储session信息,因为Token自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息。
  3. 解耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
  4. 适用性更广:只要是支持http协议的客户端,就可以使用token认证。
  5. 服务端只需要验证token的安全,不必再去获取登录用户信息,因为用户的登录信息已经在token信息中。
  6. 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT).

1.2 缺点

  1. 网络传输的数据量增大:由于token中存储了大量的用户和安全相关的信息,所以比单纯的cookie信息(例如session_id)要大很多,传输过程中需要消耗更多流量,占用更多带宽,
  2. 和所有的客户端认证方式一样,如果想要在服务端控制token的注销有难度,而且也很难解决客户端的劫持问题。
  3. 由于token信息在服务端增加了一次验证数据完整性的操作,所以比session的认证方式增加了cpu的开销。

但是整体来看,基于token的认证方式还是比session和cookie方式要有很大优势。在所知的token认证中,jwt是一种优秀的解决方案。

2. jwt结构

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。中间用点(.)分隔成三个部分。注意JWT 内部是没有换行的。

img

2.1 头部

header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

将上面的内容进行base64编码,可以得到我们JWT的头部,编码后如下:

1
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=

2.2 Payload

Payload 部分也是一个JSON对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

1
2
3
4
5
6
7
iss (issuer):该JWT的签发者
exp (expiration time):什么时候过期,这里是一个Unix时间戳
sub (subject):该JWT所面向的用户
aud (audience):接收该JWT的一方
nbf (Not Before):生效时间
iat (Issued At):在什么时候签发的
jti (JWT ID):编号

除了以上字段之外,你完全可以添加自己想要的任何字段,这里还是提醒一下,由于jwt的标准,信息是不加密的,所以一些敏感信息最好不要添加到json里面

1
2
3
4
5
6
7
8
9
{
"iss": "liuvv.com",
"iat": 1500218077,
"exp": 1500218077,
"aud": "www.liuvv.com",
"sub": "1@liuvv.com",
"user_id": "dc2c4eefe2d141490b6ca612e252f92e",
"user_token": "09f7f25cdb003699cee05759e7934fb2"
}

现在我们需要将负载这整个部分进行base64编码,编码后结果如下:

1
ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9

2.3 Signature

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥(这个秘钥只有服务端知道),签名算法是header中指定的那个,然对它们签名即可。

1
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

首先需要将头部和负载通过.链接起来就像这样:header.Payload,上述的例子链接起来之后就是这样的:

1
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9

由于HMacSHA256加密算法需要一个key,我们这里key暂时用liuvv吧

加密后的内容为:

1
686855c578362e762248f22e2cc1213dc7a6aff8ebda52247780eb6b5ae91877

对上面的签名内容进行base64编码得到最终的签名

1
Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==

最终的JWT:

1
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9.Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==

可以在线玩玩:https://www.bejson.com/jwt/

3. jwt使用

3.1 流程

  1. 客户端携带用户的登录凭证(一般为用户名密码)提交请求。
  2. 服务端收到登录请求,验证凭证正确性,如果正确则按照协议规定生成token信息,经过签名并返回给客户端。
  3. 客户端收到token信息,可以保存在cookie或者其他地方,以后每次请求的时候都携带上token信息。
  4. 业务服务器收到请求,验证token的正确性,如果正确则进行下一步操作。

image

3.2 交互

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。

你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

1
2
3
4
5
6
7
Authorization: Bearer <token>

fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})

3.3 特点

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。
(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

3.4 问题

  1. 用户登出,如何设置token无效?

    • 用户登出,浏览器端丢弃token
    • 使用redis数据库,用户登出,从redis中删除对应的token,请求访问时,需要从redis库中取出对应的token,若没有,则表明已经登出
  2. 两个不同的设备,一个设备登出,另外一个设备如何处理?

    • 服务器维护一个清单(记录该账号是否已经签发token),这样又回到session的老路了
    • 每一个设备与用户生成唯一的key,保存在redis中,即设备1的用户登出,只删除对应的token,设备2的token仍然存在
    • 服务器端维护一个版本号,相同用户不同设备登入,版本号加1,这样保持key的唯一性(和上面差不多)

4. 参考资料

给作者打赏,可以加首页微信,咨询作者相关问题!