JWT教程详细介绍了JWT(JSON Web Token)的定义、工作原理及其组成部分,包括头部、负载和签名。文章还深入讲解了JWT认证流程、生成JWT的方法以及验证JWT的有效性和签名的步骤。此外,教程提供了JWT在Web应用和移动应用中的实际应用示例,帮助读者更好地理解和使用JWT。
1. JWT简介1.1 什么是JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用之间安全地传输信息。JWT通常用于身份验证和授权,它允许客户端通过HTTP请求携带少量的数据,这些数据经过了严格的加密处理,确保了数据的安全性。
JWT由三部分组成,分别是头部(Header)、负载(Payload)和签名(Signature),这三部分通过点号(.
)连接在一起。
1.2 JWT的工作原理
当用户登录时,服务器验证用户名和密码,然后生成一个JWT。这个JWT被发送回客户端,客户端将它存储起来,以便后续的请求中使用。客户端在发送请求时,通常会将JWT放在HTTP请求头中的Authorization
字段里。
服务器接收到请求后,会检查请求中的JWT是否有效。有效的JWT会包含一些信息,例如用户的标识符(如ID、用户名等),这些信息可以用于授权和访问控制。
1.3 JWT的组成部分
-
头部(Header):
- 包含令牌的类型(通常是
JWT
)和所使用的签名算法(如HMAC SHA256
或RSA
等)。 - 示例头部:
{ "alg": "HS256", "typ": "JWT" }
- 包含令牌的类型(通常是
-
负载(Payload):
- 包含声明(claims),声明是关于实体(人、服务、资源)的声明。
- 标准声明:例如
iss
(发行人)、sub
(主题)、aud
(受众)、exp
(过期时间)、nbf
(生效时间)、iat
(发行时间)、jti
(JWT ID)等。 - 自定义声明:开发者可以根据需要添加自定义的声明,例如用户的ID、用户名等。
- 标准声明:例如
- 示例负载:
{ "sub": "1234567890", "name": "John Doe", "admin": true }
- 包含声明(claims),声明是关于实体(人、服务、资源)的声明。
- 签名(Signature):
- 通过使用Header中指定的算法,对头部和负载的Base64编码后的字符串进行加密。
- 示例签名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
2.1 请求与响应流程
-
客户端发起登录请求:
- 客户端发送包含用户名和密码的登录请求到服务器。
-
示例请求:
POST /login HTTP/1.1 Host: localhost:3000 Content-Type: application/json { "username": "john", "password": "123456" }
-
服务器验证用户身份:
- 服务器验证用户名和密码是否正确,验证通过后,服务器生成一个JWT。
-
示例响应:
HTTP/1.1 200 OK Content-Type: application/json { "token": "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAibWFuYmVyIiwgImlhdCI6IDMxNDI3OTU2MzMsICJzdHJwIjogInRlc3QifQ.ASfzr2fZi6jPQhB9X3kx2x20b730XWzY1203jKQ" }
-
客户端携带JWT发起请求:
- 客户端获取到JWT后,将JWT存储起来,并在后续的请求中将JWT放在HTTP请求头的
Authorization
字段中。 - 示例请求:
GET /user HTTP/1.1 Host: localhost:3000 Authorization: Bearer eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAibWFuYmVyIiwgImlhdCI6IDMxNDI3OTU2MzMsICJzdHJwIjogInRlc3QifQ.ASfzr2fZi6jPQhB9X3kx2x20b730XWzY1203jKQ
- 客户端获取到JWT后,将JWT存储起来,并在后续的请求中将JWT放在HTTP请求头的
-
服务器验证JWT:
- 服务器收到请求后,会解析HTTP请求头中的JWT,验证签名的有效性,检查JWT是否过期,最后从JWT中提取出必要的信息用于后续的处理。
-
示例响应:
HTTP/1.1 200 OK Content-Type: application/json { "username": "manny", "isLoggedIn": true }
2.2 签名与验证机制
-
生成签名:
- 通过签名算法(如
HMAC SHA256
)对头部和负载进行加密。 -
示例代码:
import jwt # 生成JWT token = jwt.encode({ 'sub': '1234567890', 'name': 'John Doe', 'admin': True }, 'secret', algorithm='HS256') print(token)
- 通过签名算法(如
-
验证签名:
- 通过相同的签名算法对JWT进行验证。
-
示例代码:
import jwt # 解码和验证JWT payload = jwt.decode(token, 'secret', algorithms=['HS256']) print(payload)
3.1 使用Python生成JWT
-
安装依赖:
- 使用pip安装
PyJWT
库。 - 示例代码:
pip install PyJWT
- 使用pip安装
-
生成JWT:
- 使用
jwt.encode
方法生成JWT。 -
示例代码:
import jwt # 生成JWT token = jwt.encode({ 'sub': '1234567890', 'name': 'John Doe', 'admin': True }, 'secret', algorithm='HS256') print(token)
- 使用
3.2 使用Java生成JWT
-
安装依赖:
- 在
pom.xml
中添加jjwt
依赖。 - 示例代码:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
- 在
-
生成JWT:
- 使用
JwtBuilder
创建JWT。 -
示例代码:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; public class JwtExample { public static void main(String[] args) { String token = Jwts.builder() .setSubject("1234567890") .claim("name", "John Doe") .claim("admin", true) .signWith(SignatureAlgorithm.HS256, "secret") .compact(); System.out.println(token); } }
- 使用
3.3 使用JavaScript生成JWT
-
安装依赖:
- 使用npm安装
jsonwebtoken
库。 - 示例代码:
npm install jsonwebtoken
- 使用npm安装
-
生成JWT:
- 使用
jwt.sign
方法生成JWT。 -
示例代码:
const jwt = require('jsonwebtoken'); const token = jwt.sign({ sub: '1234567890', name: 'John Doe', admin: true }, 'secret', { algorithm: 'HS256' }); console.log(token);
- 使用
4.1 验证JWT的有效性
-
安装依赖:
- 使用pip安装
PyJWT
库。 - 示例代码:
pip install PyJWT
- 使用pip安装
-
验证JWT的有效性:
- 使用
jwt.decode
方法验证JWT的有效性。 -
示例代码:
import jwt try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) print(payload) except jwt.ExpiredSignatureError: print("Token has expired") except jwt.InvalidTokenError: print("Invalid token")
- 使用
4.2 验证JWT的签名
-
安装依赖:
- 使用pip安装
PyJWT
库。 - 示例代码:
pip install PyJWT
- 使用pip安装
-
验证JWT的签名:
- 使用
jwt.decode
方法验证JWT的签名。 -
示例代码:
import jwt try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) print(payload) except jwt.SignatureVerificationError: print("Signature verification failed") except jwt.InvalidTokenError: print("Invalid token")
- 使用
5.1 在Web应用中的使用
-
用户登录:
- 用户登录时,服务器验证用户名和密码,生成JWT并返回给客户端。
- 客户端在后续的请求中携带JWT进行认证。
-
示例代码:
from flask import Flask, request, jsonify import jwt app = Flask(__name__) @app.route('/login', methods=['POST']) def login(): username = request.json.get('username') password = request.json.get('password') if username == 'john' and password == '123456': token = jwt.encode({ 'sub': 'john', 'name': 'John Doe', 'admin': False }, 'secret', algorithm='HS256') return jsonify({'token': token}) return jsonify({'error': 'Invalid credentials'}), 401 @app.route('/user', methods=['GET']) def user(): token = request.headers.get('Authorization', '').split(' ')[1] try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) return jsonify(payload) except jwt.ExpiredSignatureError: return jsonify({'error': 'Token has expired'}), 401 except jwt.InvalidTokenError: return jsonify({'error': 'Invalid token'}), 401
-
获取用户信息:
- 客户端发送带有JWT的请求,服务器验证JWT并返回用户信息。
-
示例代码:
from flask import Flask, request, jsonify import jwt app = Flask(__name__) @app.route('/login', methods=['POST']) def login(): username = request.json.get('username') password = request.json.get('password') if username == 'john' and password == '123456': token = jwt.encode({ 'sub': 'john', 'name': 'John Doe', 'admin': False }, 'secret', algorithm='HS256') return jsonify({'token': token}) return jsonify({'error': 'Invalid credentials'}), 401 @app.route('/user', methods=['GET']) def user(): token = request.headers.get('Authorization', '').split(' ')[1] try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) return jsonify(payload) except jwt.ExpiredSignatureError: return jsonify({'error': 'Token has expired'}), 401 except jwt.InvalidTokenError: return jsonify({'error': 'Invalid token'}), 401
5.2 在移动应用中的使用
-
用户登录:
- 用户登录时,服务器验证用户名和密码,生成JWT并返回给客户端。
- 客户端在后续的请求中携带JWT进行认证。
-
示例代码:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.HashMap; import java.util.Map; public class JwtExample { public static void main(String[] args) { String token = Jwts.builder() .setSubject("john") .claim("name", "John Doe") .claim("admin", false) .signWith(SignatureAlgorithm.HS256, "secret") .compact(); System.out.println(token); } }
-
获取用户信息:
- 客户端发送带有JWT的请求,服务器验证JWT并返回用户信息。
-
示例代码:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; public class JwtExample { public static void main(String[] args) { String token = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiam9obyIsICJuYW1lIjogIkpvaG4gRG9lIiwgImFkbWluIjogIjJuaW51cyJ9.LdK2y3pZtXzmnKo6cPvXa0eU5kPfZ6iPf370Q2j"; try { Claims claims = Jwts.parser() .setSigningKey("secret") .parseClaimsJws(token) .getBody(); System.out.println(claims.getSubject()); System.out.println(claims.get("name")); System.out.println(claims.get("admin")); } catch (Exception e) { e.printStackTrace(); } } }
6.1 JWT的存储方式
JWT可以通过以下方式存储:
-
存储在前端:
- 本地存储:可以将JWT存储在浏览器的
localStorage
或sessionStorage
中。 - HTTP-only Cookie:将JWT存储在HTTP-Only Cookie中,这样只能通过服务器端获取JWT。
- 示例代码:
// 存储在localStorage localStorage.setItem('token', token);
// 存储在HTTP-only Cookie
document.cookie =token=${token}; HttpOnly; Secure; SameSite=Strict
; - 本地存储:可以将JWT存储在浏览器的
- 存储在后端:
- 将JWT存储在服务器端的会话中,当客户端需要时,通过请求获取JWT。
- 示例代码:
session['token'] = token
6.2 JWT的安全性问题
JWT的安全性问题包括但不限于以下几点:
- 密钥泄露:
- 如果密钥泄露,任何人都可以生成有效的JWT。因此,密钥需要妥善保管,避免泄露。最好将密钥存储在安全的地方,并定期更换。
- 篡改数据:
- 如果使用了不安全的算法(如
HS256
),攻击者可以通过篡改负载数据来获取敏感信息。因此,需要使用安全的算法,并确保JWT的签名算法能够防止篡改。
- 如果使用了不安全的算法(如
- 过期时间:
- 设置合理的过期时间可以减少JWT被滥用的风险。过期时间设置得太短,可能会影响用户体验;设置得太长,可能增加风险。建议设置为合理的时长,既方便用户操作,又能保证安全性。
- 跨站请求伪造(CSRF)攻击:
- 使用HTTP-only Cookie可以防止CSRF攻击。确保在前后端交互中使用HTTP-only Cookie存储JWT。
- 公开传输:
- JWT通常通过HTTP协议传输,如果使用HTTPS协议,可以防止数据在传输过程中被窃取。建议在所有网络通信中使用HTTPS协议。
- 存储位置:
- 将JWT存储在本地存储时,需要注意防止XSS攻击。确保客户端代码能够安全地处理JWT,避免恶意代码篡改或窃取JWT。
6.3 JWT与Cookie的区别
-
Cookie:
- 存储在客户端的HTTP协议头中。
- 可以设置过期时间,过期后自动删除。
- 使用HTTP-only Cookie可以防止XSS攻击,确保JWT的安全性。
- 可以存储在服务器端的会话中,但会话的生命周期受到服务器端的限制。
- 在浏览器中,Cookie可以被浏览器自动发送到服务器,方便实现后端的会话管理。
- JWT:
- 存储在客户端,可以存储在本地存储或HTTP-only Cookie中。
- 可以设置过期时间,但过期时间由JWT本身携带,更加灵活。
- JWT本身携带了签名,可以防止数据被篡改。
- 不依赖于服务器端的会话,可以在多个不同服务器之间传递,适用于分布式系统或微服务架构。
- JWT可以存储在任意客户端环境中,不仅限于浏览器,还可以用于移动应用或服务器间通信。
通过以上介绍,相信你已经对JWT有了一个全面的了解,可以开始尝试在自己的项目中使用JWT进行认证和授权了。如果还有更多疑问,可以参考相关的技术文档或在社区中寻求帮助。