Json web token

Posted CatdeXin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Json web token相关的知识,希望对你有一定的参考价值。

 

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

跨域认证问题

互联网服务离不开用户认证,一般流程是这样的

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

JWT认证流程

在项目开发中,一般会按照上图所示过程进行认证; 即: 用户登录成功之后,服务器给浏览器返回一个token,以后用户再访问浏览器携带token再去服务端发送请求,服务端效验token的合法性,合法则给用户展示数据,否则,返回一些错误信息

深入了解jwt:https://www.freebuf.com/articles/web/180874.html

传统token方式和jwt在认证方面有什么差异

  • 传统token方式
    用户登录成功以后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需要携带token,服务端接收到token之后,需要去数据库或缓存里取校验token是否超时,合法
    
  • jwt方式
    用户登录成功以后,服务端通过jwt生成一个随机token给用户(服务器无需保留token),以后用户访问时需携带token,服务端接受到token之后,通过jwttoken进行校验是否超时,是否合法
    

 

JWT创建TOKEN

jwt的原理

jwt的生成格式如下,由.连接的三段字符串组成

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva G4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

生成规则如下

  • 第一段header部分,固定包含算法的token类型,对此json进行base64加密,这就是token第一段
    • 声明类型,这里是jwt
    • 声明加密的算法,通常直接使用hmac sha256
    {
      \'typ\': \'JWT\',
      \'alg\': \'HS256\'
    }
    • 然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
  • 第二段payload部分,包含一些数据,对此json进行base64加密,得到第二部分
    • 标准中注册的声明(建议但不强制使用)
    • 公有的声明
      • 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    • 私有的声明
      • 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true,
        ....
    }
    然后将其进行base64加密,得到Jwt的第二部分
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
  • jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
    • 把前鲁昂段的base64后的密文通过.拼接起来,然后进行hs256加密,然后对hs256密文进行base64url加密,得到token第三段
    • header (base64后的)
    • payload (base64后的)
    • secret
    base64url(
        HMACSHA256(
            base64UrlEncode(header) + "." + base64UrlEncode(payload),
            your-256-bit-secret (秘钥加盐)
        )
    )

最后将三段字符串通过.拼接起来,就成了jwt的token

注意: base64url加密是先做base64加密,然后将-代替+_代替/

 

代码实现

基于python 的 pyjwt模块创建jwt的token

安装jwt

pip install pyjwt

 

import jwt
import datetime

SALT = "ASDASDADFVQWEQFq@eq!dfqwetwgsdfaCADVQEERQERWQEQ134145235!#$!#"

# 设置超时 时间
#
timeout = datetime.datetime.now() + datetime.timedelta(seconds=70) # 本地时间 timeout = datetime.datetime.utcnow() + datetime.timedelta(seconds=70) # 世界时间 def create_jwt(): # 构造header headers = { \'typ\': \'jwt\', \'alg\': \'HS256\' } """headers 中一些固定参数名称的意义""" # jku: 发送JWK的地址;最好用HTTPS来传输 # jwk: 就是之前说的JWK # kid: jwk的ID编号 # x5u: 指向一组X509公共证书的URL # x5c: X509证书链 # x5t:X509证书的SHA-1指纹 # x5t#S256: X509证书的SHA-256指纹 # typ: 在原本未加密的JWT的基础上增加了 JOSE 和 JOSE+ JSON。JOSE序列化后文会说及。适用于JOSE标头的对象与此JWT混合的情况。 # crit: 字符串数组,包含声明的名称,用作实现定义的扩展,必须由 this->JWT的解析器处理。不常见。 # 构造payload payload = { \'user_id\': 1, # 自定义用户id \'username\': \'nayue\', # 自定义用户名 \'exp\': timeout # 超时时间 } """payload 中一些固定参数名称的意义, 同时可以在payload中自定义参数""" # iss 【issuer】发布者的url地址 # sub 【subject】该JWT所面向的用户,用于处理特定应用,不是常用的字段 # aud 【audience】接受者的url地址 # exp 【expiration】 该jwt销毁的时间;unix时间戳 # nbf 【not before】 该jwt的使用时间不能早于该时间;unix时间戳 # iat 【issued at】 该jwt的发布时间;unix 时间戳 # jti 【JWT ID】 该jwt的唯一ID编号 result = jwt.encode( headers=headers, # json web token 数据结构包含两部分, payload(有效载体), headers(标头) payload=payload, # payload, 有效载体 key=SALT, # 进行加密签名的密钥 lgorithm=\'HS256\' # 指明签名算法方式, 默认也是HS256 ).decode("utf-8") # python3 编码后得到 bytes, 再进行解码(指明解码的格式), 得到一个str return result if __name__ == "__main__": token = create_jwt() print(token) # eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im5heXVlIiwiZXhwIjoxNTkxMzYzMTkyfQ.ue2bHYw6f555OiiPAj_Rmd3hKlIQNtOtYJ3unPiB80g

 

JWT校验token

一般在认证成功之后,会把jwt生成的token返回给用户,以后用户再次访问的时候就要携带token,此时jwt需要对token进行超时和合法性校验

获取token后,会按照以下步骤进行校验

  • 将token分割成header_segment,payload_segment,crypto_segment三部分
  • 对第一部分header_segment进行base64url解密,得到header
  • 对第二部分payload_segment进行base64url解密,得到payload
  • 对第三部分crypto_segment进行base64url解密,得到signature
  • 对第三部分signature部分数据进行合法性校验
    • 拼接前两段密文,即:signing_input
    • 从第一段文明中获取加密算法,默认: HS256
    • 使用 算法+盐 对siging_input进行加密,将得到的结果和signature密文进行比较
import jwt
from jwt import exceptions

def get_payload(jwt_token):
    try:
        # 需要解析的 jwt_token  密钥  使用和加密时相同的算法
        data = jwt.decode(jwt_token, SALT, algorithms=\'HS256\')
        # 解析出来的就是 payload 内的数据
        return data
    # 如果 jwt 被篡改过; 或者算法不正确; 如果设置有效时间, 过了有效期; 或者密钥不相同; 都会抛出相应的异常
    except exceptions.ExpiredSignatureError:
        return "token失效"
    except jwt.DecodeError:
        return "token认证失败"
    except jwt.InvalidTokenError:
        return "非法的Tooken"
    except Exception as error:
        return error

if __name__ == "__main__":
    # token = create_jwt()
    token = "eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im5heXVlIiwiZXhwIjoxNTkxMzM3NjA5fQ.5NHXMITig2reOFUngQg4aknHa3bT5wurVnci6NWvEQw"
    data = get_payload(token)
    print(data) # {\'user_id\': 1, \'username\': \'nayue\', \'exp\': 1591363192}
JWT总结
  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,javascript,NodeJS,php等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

注意

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

 

JSON WEB TOKEN 实战

Django案例

在用户登录成功以后,生成token并返回,用户再来访问时需携带token

此示例在django的中间件中对token进行效验,内部编写了两个中间件来支持用户通过两种方式传递token

Django rest framework案例

flask案例

以上是关于Json web token的主要内容,如果未能解决你的问题,请参考以下文章

JSON Web Tokens简单学习

JSON Web Token (JWT) Python 的实现

JSON Web Token(JWT)

java小技能:JWT(json web token)认证实现

JSON Web Token入门手册

java - 如何延长过期时间java json web token?