JWT令牌

1. JWT概述

JWTJSON Web Token,是一个开放标准,用于在各方之间安全地传输信息。并且JWT经过数字签名,安全性高。通俗来说,也就是以JSON形式作为Web应用中的令牌,用于信息传输,在数据传输过程中可以完成数据加密、签名等相关处理。

JWT由于其简洁、自包含和易于验证的特性,被广泛应用于各种场景,尤其是在分布式系统中:

  • 身份验证 Authentication):这是JWT最常见的用途。当用户成功登录后,服务器会返回一个JWT给客户端。客户端将JWT存储起来,并在后续的每个请求中携带JWT。服务器通过验证JWT,可以确认用户的身份,而无需依赖传统的SessionCookie机制,这对于构建无状态的RESTful API非常有用。
  • 授权 Authorization:一旦用户通过身份验证,JWT还可以用于授权。Payload中可以包含用户的角色、权限等信息。服务器在接收到客户端的请求后,可以通过解析JWT中的这些信息,判断用户是否有权限访问特定的资源或执行特定的操作。
  • 信息交换 Information ExchangeJWT可以在各方之间安全地传输信息。由于JWT可以被签名并且加密,因此可以确保信息的完整性和机密性。例如,在微服务架构中,不同的服务之间可以使用JWT来传递用户身份信息或其他安全相关的声明。
  • 单点登录 Single Sign-On, SSOJWT可以方便地实现SSO。当用户在一个应用中登录后,认证服务器可以生成一个包含用户身份信息的JWT,其他信任该认证服务器的应用可以通过验证这个JWT,实现用户的免登录访问。
  • 无状态的API:传统的Session机制需要在服务器端存储用户的会话信息,这在用户量大或者需要水平扩展的场景下会带来挑战。JWT是自包含的,所有的状态都存储在Token本身,服务器不需要维护会话存储,从而更容易实现无状态的 API 和服务的扩展。
  • 移动应用认证 Mobile App AuthenticationJWT非常适合移动应用与后端服务器之间的身份验证。由于JWT本身就是一个字符串,可以方便地通过HTTP Header或其他方式传递,而不需要依赖浏览器特定的Cookie机制。
  • 安全地传递临时凭证 Securely Passing Temporary Credentials:例如在用户找回密码的流程中,可以生成一个包含临时凭证和过期时间的JWT,通过邮件发送给用户。用户点击链接后,服务器验证JWT的有效性,并允许用户重置密码。

总而言之,JWT是一种强大且灵活的安全令牌,广泛应用于现代Web应用和分布式系统中,用于解决身份验证、授权和安全信息传输等问题。

2. Session认证

Session认证是一种基于服务端会话管理的身份验证机制,主要用于跟踪用户的登录状态。其核心流程如下:

  • 会话初始化:用户首次登录时,服务端验证用户名和密码后,生成唯一Session ID,并将用户数据如用户ID、权限存储在服务端内存或数据库中。
  • 凭证传递:服务端通过HTTP响应头的Set-Cookie字段将Session ID发送至客户端,浏览器自动将其保存为Cookie
  • 会话维持:客户端后续请求时,浏览器自动携带Cookie中的Session ID。服务端通过该ID查询会话存储,验证用户身份和权限。

该方式可以很好的解决身份验证的问题,但是同时也暴露了以下问题:

  • 每个用户经过应用验证之后,都需要在服务器做保存会话记录,通常来说session都是保存在内存中的,随着认证用户越多,服务端的开销也会越来越大。
  • 在分布式环境下需要同步存储【session共享】,限制了负载均衡器的能力,也限制了应用的扩展能力。
  • 基于Cookie来进行用户识别,cookie如果被截获,用户很容易遭受跨站请求伪造的攻击。
    • xss攻击:跨站脚本攻击,通过向网页注入恶意脚本,利用浏览器信任机制在用户端执行攻击代码的安全漏洞。攻击者可通过注入的脚本窃取用户敏感信息如Cookie、会话令牌或劫持用户操作。
    • xsrf攻击:利用用户已登录的身份,诱导其点击恶意链接或访问含恶意请求的页面,以用户名义执行非授权操作,如转账、修改密码。
  • 在前后端分离项目中,部署困难。

为了解决以上问题,可以使用Token认证,如JWT认证。

3. JWT认证

3.1 JWT原理

一个JWT是一个由三部分组成的字符串,这三部分之间用点 .分隔。如下:

  • Header头部:通常包含以下两部分:Header会经过Base64Url编码形成JWT的第一部分。这方便之后的解码。

    • typtoken的类型,对于JWT来说,就是JWT
    • alg:签名算法,如HS256RSA
  • Payload载荷:Payload也会经过Base64Url 编码形成JWT 的第二部分。Payload是未加密的,不要在其中放入敏感信息。

    • 包含claims声明,是关于实体和其它数据的声明。
    • Payload中可以包含三种类型的claims
      • Registered claims注册声明:这是一组预定义的声明,不是强制性的,但推荐使用,例如:
        • issJWT 的签发者。
        • subJWT 的主题。
        • audJWT 的接收方。
        • expJWT的过期时间。
        • nbf :在此时间之前,JWT 不可用。
        • iatJWT 的签发时间。
        • jtiJWT 的唯一标识符。
      • Public claims 公共声明:可以由JWT的使用者自定义,但为了避免冲突,应该在 IANA JSON Web Token Registry 中注册。
      • Private claims私有声明:由生成和使用 JWT 的双方自定义的声明,用于传递自定义信息。
  • Signature签名:用于验证 JWT 的完整性和真实性,签名是基于 HeaderPayload,使用 Header 中指定的签名算法以及一个secret 密钥生成的。

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

总的来说,JWT的生成和验证流程如下:

  • 生成JWT

    • 服务器接收到用户的登录请求并验证凭据。如果验证成功,服务器会创建一个包含用户身份信息和其他必要信息的Payload

    • 服务器选择一个签名算法,并使用该算法、Payload和一个只有服务器知道的Secret KeyHeaderPayload进行签名。

    • HeaderPayloadSignature经过Base64Url编码后用 . 连接起来,形成最终的JWT

    • 服务器将JWT返回给客户端。

  • 使用JWT进行身份验证:

    • 客户端在后续的HTTP请求中,通常通过Authorization请求头携带JWT

    • 服务器接收到请求后,会提取JWT

    • 服务器对JWTHeaderPayload进行Base64Url解码。

    • 服务器使用相同的签名算法和Secret Key,对解码后的HeaderPayload重新计算签名。服务器将重新计算的签名与JWT中包含的Signature进行比较。如果签名一致,则说明JWT没有被篡改,并且是由服务器颁发的。

    • 服务器还会验证Payload中的声明,例如 exp ,以确保JWT仍然有效。

    • 如果验证通过,服务器就认为客户端是经过身份验证的,并根据JWT中包含的信息授予相应的访问权限。

相较于传统的Session相比,JWT有以下优势:

  • 无状态:Session机制需要在服务端存储用户的会话信息,但是JWT是自包含的,用户的状态信息都存储在Token本身【payload中】,且该Token存储在客户端。这样就降低了服务器的压力,提高性能。
  • 可扩展性:不同的服务之间可以共享相同的密钥来验证JWT,而无需共享Session存储或进行跨服务的Session查询,很适合分布式应用。这降低了服务之间的耦合性,提高系统的可扩展性。
  • 跨域/跨平台:传统的Session跨域请求时可能会受到限制,如何浏览器的同源策略。JWT作为一个独立的Token字符串,可以方便的通过请求头Authorization或请求体在不同的域或平台传递,具有更好的跨域和跨平台兼容性。
  • 更适合移动应用:移动应用通常不依赖浏览器的Cookie机制来管理SessionJWT可以很方便的存储在移动应用的本地存储中,并通过自定义请求头发送给服务器,与后端api进行身份验证。

4. 代码实现

import time

# Create your tests here.
import jwt
import datetime


def create_token(secret_key, user_id, username, exp=10):
    """生成JWT令牌"""
    # 定义payload
    payload = {
        'user_id': user_id,
        'username': username,
        # 设置过期时间
        'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=exp),
        # jwt的签发时间
        'iat': datetime.datetime.utcnow()
    }

    token = jwt.encode(payload, secret_key, algorithm='HS256')
    return token


def verify_token(token, secret_key):
    """验证JWT令牌"""
    try:
        payload = jwt.decode(token, secret_key, algorithms=['HS256'])
        print("JWT OK")
    except jwt.ExpiredSignatureError:
        print("JWT has expired")
        return None
    except jwt.InvalidSignatureError:
        print("Invalid JWT signature")
        return None
    except jwt.DecodeError:
        print("Invalid JWT format")
        return None


if __name__ == '__main__':
    # 安全密钥
    SECRET_KEY = 'ashkjfhaafjh+@@safdnsjkf'
    user = {
        'id': 1,
        'username': 'admin'
    }
    # 生成JWT令牌
    token = create_token(SECRET_KEY, user['id'], user['username'])

    # 验证JWT令牌
    payload1 = verify_token(token, SECRET_KEY) # JWT OK

    time.sleep(12)
    payload2 = verify_token(token, SECRET_KEY) # JWT has expired

你可能感兴趣的:(后端)