JWT认证方案与禁用令牌策略

认证方案

1.1 jwt

  • 对比状态保持机制

    • APP不支持状态保持
    • 状态保持有同源策略, 无法跨服务器传递
  • 不可逆加密

    • md5 sha1 sha256
    • 主要用于数据认证, 防止数据被修改
  • 消息摘要 MD

    • 通过哈希算法将任意长度内容转为定长内容, 且相同内容的哈希值始终相同, 不同内容的哈希值不同(极小概率出现碰撞)

    • 由于其唯一性, 一般将数据的哈希值称为数据的摘要信息, 称为数据的"指纹", 用于检测数据是否被修改

    • 代表算法 sha1 sha256 md5

    • 缺点

      • 哈希算法是公开的, 如果可以获取到明文, 就可以穷举出使用的算法
  • 消息认证 MA

    • 哈希算法基础上混入秘钥, 防止哈希算法被破解, 避免签名被伪造

    • 代表算法 hmacsha256

    • JWT一般会采用 消息认证 机制

      • 一般的web应用, 不会将秘钥交给客户端 ,也就表示客户端不会验签服务器的身份
    • 缺点

      • 一旦秘钥泄露, 仍然可以伪造签名
  • 数字签名

    • 利用非对称加密对摘要信息进行加密, 避免摘要信息被伪造

    • 非对称加密采用秘钥对

      • 公钥和私钥
      • 公钥加密, 私钥解密
      • 私钥加密, 公钥解密
      • 私钥可以推出公钥, 公钥无法推出私钥
    • 发送者使用私钥对数据摘要加密(签名), 接收者使用对应的公钥解密, 然后对数据进行哈希处理, 比对摘要信息是否一致

    • 代表算法 RSA

    • 使用场景

      • 安全级别要求比较高的系统, 如银行等
    • 优点

      • 客户端不会像消息认证一样保存秘钥, 而是保存了非对称加密的公钥, 即使客户端被破解, 公钥被获取, 也无法通过公钥生成合法的签名
    • 缺点

      • 效率低

1.2 PyJWT

  • 安装 pip install PyJWT

    import jwt
    from datetime import datetime, timedelta
    from jwt import PyJWTError
    
    # 包装数据  jwt的规范中要求通过exp参数来设置有效期, 要求有效期使用格林尼治时间
    payload = {'payload': 'test', 'exp': datetime.utcnow() + timedelta(seconds=30)}
    
    key = 'secret'
    # # 生成jwt
    # token = jwt.encode(payload, key, algorithm='HS256')
    # print(token)
    
    
    token = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoidGVzdCIsImV4cCI6MTU2MjgwOTkzMn0.BSc0A2ibdjHTlmW7wtWfj5ZGkny8RX8tV12313'
    # 验证jwt    pyjwt内部对有效期进行了验证, 如果超过时间, 会报错
    try:
        ret = jwt.decode(token, key, algorithms='HS256')
        print(ret)
    except PyJWTError as e:
        print("jwt认证失败")
    
  • 数字签名

    • 使用openssl 生成RSA秘钥对

      # 生成私钥,指定私钥的长度为2048bit   1024基本安全, 2048非常安全
      openssl genrsa -out rsa_private_key.pem 2048
      # 根据私钥生成对应的公钥
      openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key_2048.pub
      # 私钥转化成pkcs8格式, 非必须,pkcs8格式解析起来更方便
      openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt > rsa_private_key_pkcs8.pem
      
      import jwt
      
      """服务器使用私钥生成签名  对数据摘要加密 称为 签名"""
      # with open('rsa_private_key_pkcs8.pem', 'rb') as f:
      #     private_key = f.read()
      #     # 生成数字签名
      #     encoded = jwt.encode({'some': 'payload'}, private_key, algorithm='RS256')
      #     print(encoded)
      
      
      """客户端用公钥验签"""
      encoded = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.S3gxuFvPiYk2752deTDm6qupj53S0b_-WvFZLKnWzLgDTFjFF_uiwmI6GAT1mKaNvWIyxFQ1PMUPxjkdLuJpGbN3hpHM_eKaQNm_RTvY8UUh6tvq8kpH4FAF2WOglwQK9f3nS8R73PrYhQFDHcfSEBWoJJPva_Pb3YEPMawUTPmd8aeS2uma4n9JqaZwCWm1GE-6S0lKNHp7ZWMlxb5E1R_FgSLIiE3qQq-mWsweyMRtsyCBCaB1W6Y24EYuDW0KHu6k6jGZdwwABVuwyKXKVTTf_XvxM3X41ggpY6mkarSXZsF3-Aw_jWOUBHy9VBHfPCeklur6oMfyGT4FQzkcQQ'
      
      
      with open('rsa_public_key_2048.pub', 'rb') as f:
          public_key = f.read()
          decoded = jwt.decode(encoded, public_key, algorithms='RS256')
          print(decoded)
      

1.3 JWE

  • 可逆加密

    • 对称加密
      • 代表算法 des 3des aes
    • 非对称加密
      • 代表算法 RSA
      • 慢, 不适合大型数据加密
      • 加密时, 一般公钥加密, 私钥解密, 与签名相反
      • 一般私有只有一方持有, 公钥则可以多方持有(公钥公开)
      • 私钥唯一, 使用私钥签名, 公钥验签, 可以保证签名者身份唯一
        • 加密时, 私钥解密, 保证可以解密者唯一
      • 生成方式 openssl
    • 主要用于数据加密
  • 最佳方案JWE

    • 传输的数据使用对称加密, 生成数据密文, 对称加密秘钥是随机的
    • 为了防止数据篡改, 对数据密文进行摘要认证(一般使用消息认证), 摘要认证的秘钥也是随机的
    • 对称加密的秘钥 和 摘要认证的秘钥 使用非对称加密进行处理
    • JWE的耗时远高于JWS
    • 用于金融领域

åˆå¯¹ç§°åŠ å¯†å†éžå¯¹ç§°åŠ å¯†

  • 安装 pip install authlib

    
    

1.4 refresh token

1.4.1 刷新token实现流程

refreash_token

  • 特点
    • 访问令牌虽然使用频繁, 但是有效期短, 只有两个小时
    • 刷新令牌有效期长, 但是访问次数少, 可以减少泄露的风险
1.4.2 登录接口
  • 接口设计

ç™"录接口设计

  • 视图逻辑
  • 生成令牌
1.4.3 访问控制
  • 对于所有的接口都需要获取认证信息 使用请求钩子实现
  • 对于指定的接口进行访问控制 使用装饰器
  • 请求钩子和装饰器

[外链图片转存失败(img-zvgzlZNE-1562944375228)(Untitled.assets/image-20190711122529413.png)]

1.4禁用令牌

  • 需求场景

    • 用户修改密码, 需要颁发新的token, 禁用还在有效期的旧token
    • 后台封禁用户
  • 逻辑

    • 禁用旧密码的令牌
    import random
    
    from redis import StrictRedis
    
    redis_client = StrictRedis()
    
    user_id = 111
    key = 'user:{}'.format(user_id)
    token = None  # 记录token
    
    
    def update_pwd():
        """修改密码"""
        print('修改完成密码')
    
        # 先删除已有的白名单
        if redis_client.exists(key):
            redis_client.delete(key)
    
        # 将修改了密码的用户记录到白名单中
        redis_client.sadd(key, 1)
        # 设置有效期
        redis_client.expire(key, 60 * 60 * 2)
    
    
    def login():
        """登录"""
        print('登录成功')
    
        # 生成新的token
        global token
        token = random.randint(100, 999)
    
        # 判断是否有该用户对应的白名单
        if redis_client.exists(key):
            # 将token加入到白名单中
            redis_client.sadd(key, token)
    
    
    def verify():
        """校验认证"""
        print('验证token成功')
        # 判断该用户是否有白名单(如果有, 说明修改过密码)
        if redis_client.exists(key):
            # 判断该token是否在白名单中
            if redis_client.sismember(key, token):
                # 如果在, 允许访问
                print("是新token, 允许访问")
            else:
                # 如果不在, 重新登录
                print('是旧token, 需要重新登录')
        else:
            print("没有修改过密码. 允许访问")
    
    if __name__ == '__main__':
        update_pwd()
        login()
        verify()
    

参考:https://www.cnblogs.com/yblackd/p/12254209.html

原文地址:https://www.cnblogs.com/yblackd/p/14533406.html