JWT令牌
1. JWT
概述
JWT
即JSON Web Token
,是一个开放标准,用于在各方之间安全地传输信息。并且JWT
经过数字签名,安全性高。通俗来说,也就是以JSON
形式作为Web
应用中的令牌,用于信息传输,在数据传输过程中可以完成数据加密、签名等相关处理。
JWT
由于其简洁、自包含和易于验证的特性,被广泛应用于各种场景,尤其是在分布式系统中:
- 身份验证
Authentication)
:这是JWT
最常见的用途。当用户成功登录后,服务器会返回一个JWT
给客户端。客户端将JWT
存储起来,并在后续的每个请求中携带JWT
。服务器通过验证JWT
,可以确认用户的身份,而无需依赖传统的Session
或Cookie
机制,这对于构建无状态的RESTful API
非常有用。 - 授权
Authorization
:一旦用户通过身份验证,JWT
还可以用于授权。Payload
中可以包含用户的角色、权限等信息。服务器在接收到客户端的请求后,可以通过解析JWT
中的这些信息,判断用户是否有权限访问特定的资源或执行特定的操作。 - 信息交换
Information Exchange
:JWT
可以在各方之间安全地传输信息。由于JWT
可以被签名并且加密,因此可以确保信息的完整性和机密性。例如,在微服务架构中,不同的服务之间可以使用JWT
来传递用户身份信息或其他安全相关的声明。 - 单点登录
Single Sign-On, SSO
:JWT
可以方便地实现SSO
。当用户在一个应用中登录后,认证服务器可以生成一个包含用户身份信息的JWT
,其他信任该认证服务器的应用可以通过验证这个JWT
,实现用户的免登录访问。 - 无状态的
API
:传统的Session
机制需要在服务器端存储用户的会话信息,这在用户量大或者需要水平扩展的场景下会带来挑战。JWT
是自包含的,所有的状态都存储在Token
本身,服务器不需要维护会话存储,从而更容易实现无状态的 API 和服务的扩展。 - 移动应用认证
Mobile App Authentication
:JWT
非常适合移动应用与后端服务器之间的身份验证。由于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
的第一部分。这方便之后的解码。typ
:token
的类型,对于JWT
来说,就是JWT
。alg
:签名算法,如HS256
、RSA
。
-
Payload
载荷:Payload
也会经过Base64Url
编码形成JWT
的第二部分。Payload是未加密的,不要在其中放入敏感信息。- 包含
claims
声明,是关于实体和其它数据的声明。 Payload
中可以包含三种类型的claims
:Registered claims
注册声明:这是一组预定义的声明,不是强制性的,但推荐使用,例如:iss
:JWT
的签发者。sub
:JWT
的主题。aud
:JWT
的接收方。exp
:JWT
的过期时间。nbf
:在此时间之前,JWT
不可用。iat
:JWT
的签发时间。jti
:JWT
的唯一标识符。
Public claims
公共声明:可以由JWT
的使用者自定义,但为了避免冲突,应该在IANA JSON Web Token Registry
中注册。Private claims
私有声明:由生成和使用JWT
的双方自定义的声明,用于传递自定义信息。
- 包含
-
Signature
签名:用于验证JWT
的完整性和真实性,签名是基于Header
和Payload
,使用Header
中指定的签名算法以及一个secret 密钥生成的。HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
总的来说,JWT
的生成和验证流程如下:
-
生成
JWT
:-
服务器接收到用户的登录请求并验证凭据。如果验证成功,服务器会创建一个包含用户身份信息和其他必要信息的
Payload
。 -
服务器选择一个签名算法,并使用该算法、
Payload
和一个只有服务器知道的Secret Key
对Header
和Payload
进行签名。 -
Header
、Payload
和Signature
经过Base64Url
编码后用.
连接起来,形成最终的JWT
。 -
服务器将
JWT
返回给客户端。
-
-
使用
JWT
进行身份验证:-
客户端在后续的
HTTP
请求中,通常通过Authorization
请求头携带JWT
。 -
服务器接收到请求后,会提取
JWT
。 -
服务器对
JWT
的Header
和Payload
进行Base64Url
解码。 -
服务器使用相同的签名算法和
Secret Key
,对解码后的Header
和Payload
重新计算签名。服务器将重新计算的签名与JWT
中包含的Signature
进行比较。如果签名一致,则说明JWT
没有被篡改,并且是由服务器颁发的。 -
服务器还会验证
Payload
中的声明,例如exp
,以确保JWT
仍然有效。 -
如果验证通过,服务器就认为客户端是经过身份验证的,并根据
JWT
中包含的信息授予相应的访问权限。
-
相较于传统的Session
相比,JWT
有以下优势:
- 无状态:
Session
机制需要在服务端存储用户的会话信息,但是JWT
是自包含的,用户的状态信息都存储在Token
本身【payload
中】,且该Token
存储在客户端。这样就降低了服务器的压力,提高性能。 - 可扩展性:不同的服务之间可以共享相同的密钥来验证
JWT
,而无需共享Session
存储或进行跨服务的Session
查询,很适合分布式应用。这降低了服务之间的耦合性,提高系统的可扩展性。 - 跨域/跨平台:传统的
Session
跨域请求时可能会受到限制,如何浏览器的同源策略。JWT
作为一个独立的Token
字符串,可以方便的通过请求头Authorization
或请求体在不同的域或平台传递,具有更好的跨域和跨平台兼容性。 - 更适合移动应用:移动应用通常不依赖浏览器的
Cookie
机制来管理Session
。JWT
可以很方便的存储在移动应用的本地存储中,并通过自定义请求头发送给服务器,与后端api
进行身份验证。
4. 代码实现
import time# Create your tests here.
import jwt
import datetimedef create_token(secret_key, user_id, username, exp=10):"""生成JWT令牌"""# 定义payloadpayload = {'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 tokendef 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 Noneexcept jwt.InvalidSignatureError:print("Invalid JWT signature")return Noneexcept jwt.DecodeError:print("Invalid JWT format")return Noneif __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 OKtime.sleep(12)payload2 = verify_token(token, SECRET_KEY) # JWT has expired