Skip to content

JWT全称JSON Web Token

应用流程

  1. 客户端使用用户名和密码请求登录,服务端收到请求
  2. 验证用户名和密码正确后,后端通过JWT机制,将用户数据作为JWTPayload,同时在前面拼接上一个JWT Header之后进行Base64编码,并进行签名,生成一个token,格式为header.payload.signature,返回给客户端
  3. 客户端后续的每次请求都需要携带token,携带在HTTP Header Authorization
  4. 后端拿到客户端传递的token后,进行解密验证身份,验证其有效性,检查签名是否正确,是否过期,最后解析出JWT Token中的用户信息

优点

  • 无状态,token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力

  • 分布式友好,由于session需要固定保存在一个地方,如果保存在本机,分布式系统中认证会失效,如果采用redis等统一保存session,系统复杂性会增加

  • 支持跨域访问,跨域后不会存在信息丢失问题

  • CDN友好,可以通过内容分发网络请求服务端的所有资料

  • 移动端友好,当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多

  • 无需考虑CSRF(Cross Site Request Forgery跨站点请求伪造),token是开发者为了防范csrf而特别设计的令牌,浏览器不会自动添加到headers里,攻击者也无法访问用户的token,所以攻击者提交的表单无法通过服务器认证,也就无法形成攻击

    CSRF简述

    1. 在一个浏览器中打开了两个标签页,其中一个页面通过窃取另一个页面的 cookie 来发送伪造的请求,因为cookie 是随着浏览器请求自动发送到服务端的,这个是CSRF攻击成功的核心原因
    2. session认证本质需要依赖Cookie,如果cookie被截获,用户很容易受到跨站请求伪造攻击
    3. CSRF无法直接窃取到用户的Cookie,header,仅仅是冒用Cookie

缺点

  • 不可控,由于JWT无状态,想要在JWT 有效期内废弃一个JWT或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以,如果要避免这个问题,需要把JWT存入redis等缓存,JWT失效的时候就删除这个JWT,每次验证的时候查询一下JWT在不在redis,但是这样就增加了成本和系统复杂度
  • token续签不方便,JWT本身payload参数当中携带exp参数表示过期时间,payload修改之后签名也需要修改,所以需要重新生成一个JWT
  • JWT令牌一般会比较长,如果是性能极度敏感的话需要在意这一点

组成

JWT生成的Token由三部分组成:header.payload.signature,通俗地说,JWT的本质是一个字符串,将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输

  • header

    • alg:指定signature采用的加密算法,默认是HS256(HMAC SHA256),对称加密(加密和解密的密钥相同)
    • typ:固定值,通常是JWT
    • 通常值是{"alg": "HS256", "typ": "JWT"}, 通过base64Url算法进行编码之后进行拼接
  • payload

    • 用户idname

    • 默认携带iat,令牌签发时间(时间戳)

    • exp设置令牌过期时间

    • 参数一般形式如下,通过base64Url算法进行编码与header进行拼接,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,JWT只是适合在网络中传输一些非敏感的信息,要传递一些敏感数据的话需要使用一些AES或者其他类型的算法,尽量加上一些salt进行加密payload

      json
      {
        "sub": "1234567890",
        "name": "Helen",
        "admin": true
      }
      {
        "sub": "1234567890",
        "name": "Helen",
        "admin": true
      }
  • signature

    • 设置一个secretKey,通过将前两个结果合并后进行HS256算法,signature = HS256(base64Url(header)+'.'+base64Url(payload),secretKey)
    • secreKey一定不能暴露,因为可以颁发token,也可以解密

跨域资源共享

是一种基于 HTTP头的机制,该机制主要是为了避免跨站脚本攻击而存在

实际网站开发过程当中需要从A网站访问到另外一个网站B,就需要通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源,服务器需要返回一个HTTP Header Access-Control-Allow-Origin: * 表明,该资源可以被任意外源访问,这样浏览器才会正常加载返回的数据

但是当携带cookie进行访问的时候就不能返回一个Header头表示允许被任意外源访问

  • 服务器不能Access-Control-Allow-Origin 的值设为通配符“*”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com,如果值被设置为*,请求会失败,如歌设置为具体的域请求成功
  • 服务器不能Access-Control-Allow-Headers 的值设为通配符“*”,而应将其设置为标头名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • 服务器不能Access-Control-Allow-Methods 的值设为通配符“*”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET

详情参考Mozilla对跨域资源共享的定义

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

python实现

依赖项目pyjwt

https://github.com/jpadilla/pyjwt
https://github.com/jpadilla/pyjwt

安装

shell
$ pip install pyjwt
$ pip install pyjwt

使用

shell
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}

参考阅读

掘金-JWTToken详解

掘金-JWT详解

Mozilla 跨源资源共享CORS

Last updated:

Released under the MIT License.