jwt

什么是 jwt

jwt 全称 json  web token,是一种基于JSON的、用于在网络上安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。

由于http 协议是一种无状态的协议,无法对客户端的身份进行认证和唯一标识,所以需要采用其他的手段来实现。

传统的session方案和jwt方式实现

传统的cookie-sessio机制也是为了解决客户端的身份验证而出现的,服务器同通过保存客户端的数据,并发送给客户端一个随机字符串作为session_id,这样每次请求时服务器可以根据每个客户端的session_id来找到保存在服务器的数据,从而实现客户端的身份认证,但是这种方式在现在的使用种存在问题,例如session是保存在服务器的内存中(保证效率)的,这样会耗费大量的服务器内存资源,并且在目前的分布式的服务中,session存在与单独一台服务器中,而这个session无法与其他主机共享,已经登录的客户端请求转到其他服务器上将会丢失客户端状态。 

上述问题的存在总的来说还是session保存到服务端,并且没有进行共享而导致的,解决的方案是使用单独的session服务,保证session信息共享,这也是常用的做法,例如使用一个redis集群作为session储存的解决方案。

另一种解决方式则是使 jwt 的方案,它不同于将客户端的信息保存到服务器,而是通过一个jwt字符串将这些数据发送到对应的客户端,让每个客户端各自保存自己的数据,下一次请求服务器时,带上这个jwt字符串,从中获取这个字符串中获取数据即可,并且为了解决数据保存到客户端而存在被篡改的问题,jwt 使用一个算法将 数据+盐 生成一个签名添加到字符串中,这个签名只有知道盐的服务器了可以生成能被自己验证通过的签名。所以客户端想要篡改数据,就必须能够根据数据生成这个签名,也就需要服务器的这个盐。只要盐是安全的,他人就只能通过其他方式破解,这种概率是比较低的。

jwt的组成

jwt是一个固定格式的,以点分成三段的字符串,形式如下。

aDJIGghOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpSDGfvaG4gRG9lSGEIiwiaDSGWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

三个部分有各自的作用,前两部分都是可以通过BASE64反解得到明文数据的,而第三部分则是更具前两部分生成的签名。分别介绍以下三部分的内容以及生成过程。

第一部分

第一段被称为HEADER部分,包含固定的算法和token类型数据,然后将该数据的json字符串进行base64url编码得到。该头信息说明了这是一个JWT类型的token,并且在生成签名时候,使用的是HS256算法。

{
  "alg": "HS256",
  "typ": "JWT"
}

第二部分

第二部分被称为payload部分,这个部分可以包含用户的个人信息内容,用于保存用户的状态信息等,然后同样的通过base64url编码,例如可以记录用户的id,姓名等,但是不能保存敏感的信息,例如密码等,因为这部分数据可以被反解,所以等同于明文保存,如果该字符串被其他恶意用户获得,将会造成严重的安全问题。

{
  "UserId": "1001",
  "Name": "tom",
  "iat": 1516239022
  ...
}

数据使用json的格式保存即可,示例中UserId和Name都是自定义的数据,而iat是JWT的保留字,该key代表了特殊的含义,即该字符串签发时的时间戳信息,同时还可以指定过期时间等信息,这些保留的关键字包括:

iss(Issuser):代表这个JWT的签发主体;
sub(Subject):代表这个JWT的主体,即它的所有人;
aud(Audience):代表这个JWT的接收对象;
exp(Expiration time):是一个时间戳,代表这个JWT的过期时间;
nbf(Not Before):是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的;
iat(Issued at):是一个时间戳,代表这个JWT的签发时间;
jti(JWT ID):是JWT的唯一标识。

第三部分

第三部分即签名,签名由生成该字符串的服务端生成。生成的过程是将前两部分的Base64url编码后的内容通过点拼接起来,然后对其进行HS256进行加密,再进行base64编码,得到第三部分的内容。

base64url(
    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      your-256-bit-secret (秘钥加盐)
    )
)

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

python实现

了解了jwt的生成方式,即可以根据上述的方式来生成一个合法的jwt字符串,而这个功能已经有现成的模块实现,即pyjwt模块,使用该模块即可简单生成jwt字符串和验证jwt,如果需要了解具体过程,查看pyjwt源码即可。

安装模块

pip3 install pyjwt

生成jwt

import jwt
import datetime
from jwt import exceptions
SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
def create_token():
    # 第一部分内容,构造header
    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }
    # 第二部分内容 构造payload
    payload = {
        'user_id': 1, # 自定义用户ID
        'username': 'wupeiqi', # 自定义用户名
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间
    }

   # 第三部分,使用前两部分内容,加盐(SALT),并进行BASEurl64编码得到jwt result
= jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8') return result if __name__ == '__main__': token = create_token() print(token)

校验jwt

服务器得到用户的jwt后,需要判断该数据的合法性,通过验证签名,确保数据没有被篡改。同时验证是否过期,验证成功后,从第二部分中提取内容即可。

详细过程:

  • 将token分割成 headerpayloadcrypto 三部分。
  • 对第一部分header进行base64url解密,得到header

  • 对第二部分payload进行base64url解密,得到payload

  • 对第三部分crypto进行base64url解密,得到signature

  • 验证第三部分signature部分数据

    • 使用 . 拼接前两段密文。
    • 从第一段明文中获取加密算法,默认:HS256
    • 使用 加密算法 对 盐 + header + payload 进行加密,将得到的结果和signature密文进行比较。如果相同,则校验通过
import jwt
import datetime
from jwt import exceptions
def get_payload(token):
    """
    根据token获取payload
    :param token:
    :return:
    """
    try:
        # 从token中获取payload【不校验合法性】
        # unverified_payload = jwt.decode(token, None, False)
        # print(unverified_payload)
        # 从token中获取payload【校验合法性】
        verified_payload = jwt.decode(token, SALT, True)
        return verified_payload
    except exceptions.ExpiredSignatureError:
        print('token已失效')
    except jwt.DecodeError:
        print('token认证失败')
    except jwt.InvalidTokenError:
        print('非法的token')
if __name__ == '__main__':
    token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU"
    payload = get_payload(token)

通过jwt模块的decode方法将会自动对jwt的过期时间,数据的合法性等等进行检验(需要指定特定的参数),如果校验通过则返回payload数据。否则抛出异常。得到payload数据后,将这个字符串解析为的字典格式就能方便的提取内部的内容了。

posted @ 2020-07-28 22:08  没有想象力  阅读(535)  评论(0编辑  收藏  举报