JWT資料:新手入門指南
本文详细介绍了一种开放标准,JSON Web Token (JWT),用于安全地传输信息,广泛应用于身份验证和信息交换。JWT由Header、Payload和Signature三部分组成,具备简洁性、安全性和可扩展性等优势。文章详细解释了JWT的工作原理、组成部分以及在不同框架中的使用方法,并提供了实例代码演示和测试方法。
JWT简介什么是JWT
JSON Web Token (JWT) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT是一种紧凑、自包含的方式,用于实现信息的安全传输。JWT通常用于身份验证和信息交换。JWT由三部分组成,分别是Header、Payload和Signature,它们通过点号(.)分开,形成一个紧凑的字符串。
JWT的工作原理
JWT的工作原理可以简单地分为生成、传输和验证三个步骤:
- 生成JWT:首先,客户端发送一个请求到服务器以获取JWT。服务器验证客户端的身份(例如用户名和密码),如果验证成功,服务器会创建一个JWT并返回给客户端。
- 传输JWT:客户端收到JWT后,通常会将其存储在浏览器的本地存储(如localStorage或sessionStorage)中,或在每次请求中将JWT附加到HTTP头的Authorization字段中。
- 验证JWT:服务器收到请求后,会检查JWT的有效性。有效JWT会包含一些声明(claims),比如过期时间等。服务器验证JWT后,会信任JWT中的信息,继续执行请求。
JWT的优势和应用场景
JWT具有以下优势和应用场景:
- 简洁性:JWT是一个单一的字符串,可以轻松地存储和传递。
- 安全性:JWT使用加密签名来保证内容的完整性。
- 可扩展性:JWT的Payload部分可以包含自定义的信息,可以根据需要进行扩展。
- 无状态性:由于JWT可以包含必要的身份信息,因此服务器端不需要保存用户状态信息。
- 跨域支持:JWT可以在不同的域之间传递,支持跨域请求。
JWT的应用场景
JWT常用于以下几个场景:
- 身份验证:用户登录后,服务器可以生成一个JWT,客户端在后续的请求中使用这个JWT来验证身份。
- 授权:JWT可以包含用户的角色信息,用于权限控制。
- 单点登录:使用JWT可以在多个微服务或系统之间实现统一的身份验证。
- 会话管理:JWT可以替代传统的Session机制,简化客户端会话管理。
Header
JWT的Header部分包含了两种主要的信息:类型(Type)和加密算法(Algorithm)。例如,通常使用HMAC SHA-256或RSA算法。Header的格式如下:
{
"typ": "JWT",
"alg": "HS256"
}
Payload
Payload部分包含了JWT声明(Claims)的主体。JWT声明分为三类:注册声明(Registered Claims)、公共声明(Public Claims)和私有声明(Private Claims)。注册声明是一些预定义的声明,例如iss
(发行者)、sub
(主题)、aud
(受众)、exp
(过期时间)、nbf
(生效时间)、iat
(签发时间)等。例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1627545600
}
生成Payload的代码示例:
import time
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
Signature
Signature部分是通过对Header和Payload使用指定的算法进行签名生成的。签名算法使用的密钥通常是保密的。例如,使用HMAC SHA-256算法的签名过程大致如下:
生成JWT签名的完整代码示例:
import hmac
import base64
import json
import hashlib
def generate_jwt(header, payload, secret_key):
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')).rstrip(b'=')
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')).rstrip(b'=')
message = header_encoded + b"." + payload_encoded
signature = hmac.new(secret_key, message, hashlib.sha256).digest()
signature_encoded = base64.urlsafe_b64encode(signature).rstrip(b'=')
return header_encoded + b"." + payload_encoded + b"." + signature_encoded
header = {
"typ": "JWT",
"alg": "HS256"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
secret_key = b"secret_key"
jwt_token = generate_jwt(header, payload, secret_key)
print(jwt_token.decode('utf-8'))
如何使用JWT进行身份验证
创建JWT
创建JWT通常涉及三个步骤:构建Header、构建Payload、生成Signature。
import hmac
import base64
import json
import time
import hashlib
def generate_jwt(header, payload, secret_key):
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')).rstrip(b'=')
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')).rstrip(b'=')
message = header_encoded + b"." + payload_encoded
signature = hmac.new(secret_key, message, hashlib.sha256).digest()
signature_encoded = base64.urlsafe_b64encode(signature).rstrip(b'=')
return header_encoded + b"." + payload_encoded + b"." + signature_encoded
header = {
"typ": "JWT",
"alg": "HS256"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
secret_key = b"secret_key"
jwt_token = generate_jwt(header, payload, secret_key)
print(jwt_token.decode('utf-8'))
将JWT存储在浏览器的本地存储中的代码示例:
localStorage.setItem('jwt', jwt_token);
将JWT附加到HTTP头的Authorization字段中的代码示例:
const fetchWithToken = (url, options) => {
const token = localStorage.getItem('jwt');
const authOptions = {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`
}
};
return fetch(url, authOptions);
};
解码JWT
解码JWT通常需要将JWT字符串分割成Header、Payload和Signature三部分,然后对Payload部分进行解码和验证。下面是一个简单的解码JWT的示例:
import base64
import json
def decode_jwt(token):
parts = token.split('.')
header = base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8')
payload = base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8')
signature = base64.urlsafe_b64decode(parts[2] + '===')
return {
"header": json.loads(header),
"payload": json.loads(payload),
"signature": signature
}
jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTYyNzU0NTYwMCJ9.d8sasasdsadasdasd"
decoded_jwt = decode_jwt(jwt_token)
print(decoded_jwt)
验证JWT
验证JWT通常涉及以下几个步骤:
- 检查JWT的格式是否正确。
- 检查JWT的过期时间。
- 检查JWT的签名是否正确。
下面是一个简单的验证JWT的示例:
import base64
import json
import hmac
import time
def verify_jwt(token, secret_key):
parts = token.split('.')
header = base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8')
payload = base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8')
signature = base64.urlsafe_b64decode(parts[2] + '===')
expected_signature = hmac.new(secret_key, base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8') + "." + base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'), hashlib.sha256).digest()
if expected_signature != signature:
return False, "Signature verification failed"
payload_data = json.loads(payload)
if 'exp' in payload_data and time.time() > payload_data['exp']:
return False, "JWT has expired"
return True, "JWT is valid"
jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTYyNzU0NTYwMCJ9.d8sasasdsadasdasd"
secret_key = b"secret_key"
is_valid, message = verify_jwt(jwt_token, secret_key)
print(message)
JWT在不同框架中的使用方法
Node.js中使用JWT
在Node.js中,通常使用jsonwebtoken
库来处理JWT。下面是一个简单的示例:
const jwt = require('jsonwebtoken');
const secret_key = "secret_key";
// 创建JWT
const payload = {
sub: "1234567890",
name: "John Doe",
admin: true,
exp: Math.floor(Date.now() / 1000) + 3600 // 过期时间,当前时间加上3600秒
};
const token = jwt.sign(payload, secret_key, { algorithm: 'HS256' });
console.log(token);
// 验证JWT
jwt.verify(token, secret_key, (err, decoded) => {
if (err) {
console.log("JWT验证失败:", err.message);
} else {
console.log("JWT验证成功:", decoded);
}
});
Spring Boot中使用JWT
在Spring Boot中,使用JWT通常涉及到配置WebSecurityConfigurerAdapter
和自定义的JwtTokenFilter
。下面是一个简单的示例:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenUtil jwtTokenUtil;
private UserDetailsService userDetailsService;
public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) {
this.jwtTokenUtil = jwtTokenUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenUtil.getTokenFromRequest(request);
if (token != null && jwtTokenUtil.validateToken(token)) {
String username = jwtTokenUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
// 在配置文件中配置JWT
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtTokenFilter(jwtTokenUtil, userDetailsService));
}
Django中使用JWT
在Django中,使用JWT通常涉及到配置rest_framework
和djangorestframework-simplejwt
。下面是一个简单的示例:
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class MyView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({"message": "你已成功验证JWT"})
生成JWT的代码示例:
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework import status
from rest_framework.response import Response
class CustomTokenObtainPairView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
if response.status_code == status.HTTP_200_OK:
return Response({
'access': response.data['access'],
'refresh': response.data['refresh']
})
return response
验证JWT的代码示例:
class MyView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({"message": "你已成功验证JWT"})
常见问题与解决方案
JWT过期如何处理
当JWT过期时,通常需要进行重新登录或刷新JWT。刷新JWT可以使用一个长期有效的refresh token来替代。下面是一个完整的刷新JWT的代码示例:
import time
import hmac
import base64
import json
import hashlib
def refresh_jwt(token, secret_key, refresh_secret_key):
parts = token.split('.')
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'))
payload['exp'] = int(time.time()) + 86400 # 刷新过期时间,当前时间加上86400秒
new_token = generate_jwt({"typ": "JWT", "alg": "HS256"}, payload, refresh_secret_key)
return new_token
header = {
"typ": "JWT",
"alg": "HS256"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
secret_key = b"secret_key"
refresh_secret_key = b"refresh_secret_key"
jwt_token = generate_jwt(header, payload, secret_key).decode('utf-8')
print(f"生成的JWT: {jwt_token}")
new_jwt_token = refresh_jwt(jwt_token, secret_key, refresh_secret_key).decode('utf-8')
print(f"刷新后的JWT: {new_jwt_token}")
如何处理跨域问题
处理跨域问题通常涉及在服务器端配置CORS(跨域资源共享)。在Node.js中,可以使用cors
中间件来处理跨域请求。下面是一个完整的示例:
const express = require('express');
const cors = require('cors');
const app = express();
const jwt = require('jsonwebtoken');
const secret_key = "secret_key";
app.use(cors({
origin: "http://example.com", // 允许的跨域请求源
credentials: true // 允许发送凭证(如cookie)
}));
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户名和密码
const payload = {
sub: "1234567890",
name: "John Doe",
admin: true,
exp: Math.floor(Date.now() / 1000) + 3600 // 过期时间,当前时间加上3600秒
};
const token = jwt.sign(payload, secret_key, { algorithm: 'HS256' });
res.json({ token });
});
app.listen(3000, () => console.log('服务器启动'));
在Spring Boot中处理跨域问题的代码示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://example.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
在Django中处理跨域问题的代码示例:
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
from django.http import JsonResponse
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework import status
@method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def post(self, request):
# 验证用户名和密码
user = authenticate(username='username', password='password')
if user is not None:
refresh = RefreshToken.for_user(user)
return JsonResponse({
'access': str(refresh.access_token),
'refresh': str(refresh)
})
return JsonResponse({'error': '用户名或密码不正确'}, status=status.HTTP_400_BAD_REQUEST)
安全性注意事项
使用JWT时需要注意以下几点安全措施:
- 密钥安全:确保密钥不泄露,并定期更换密钥。
- 验证IP地址:在某些情况下,可以验证用户的IP地址,防止JWT被恶意使用。
- 使用HTTPS:确保所有的JWT传输都使用HTTPS协议。
- 防止CSRF攻击:使用CSRF令牌来防止用户被恶意引导到伪造页面。
实例代码演示
下面是一个完整的示例,包括用户登录、生成JWT、验证JWT和刷新JWT:
import time
import hmac
import base64
import json
import hashlib
def generate_jwt(header, payload, secret_key):
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')).rstrip(b'=')
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')).rstrip(b'=')
message = header_encoded + b"." + payload_encoded
signature = hmac.new(secret_key, message, hashlib.sha256).digest()
signature_encoded = base64.urlsafe_b64encode(signature).rstrip(b'=')
return header_encoded + b"." + payload_encoded + b"." + signature_encoded
def verify_jwt(token, secret_key):
parts = token.split('.')
header = base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8')
payload = base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8')
signature = base64.urlsafe_b64decode(parts[2] + '===')
expected_signature = hmac.new(secret_key, base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8') + "." + base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'), hashlib.sha256).digest()
if expected_signature != signature:
return False, "Signature verification failed"
payload_data = json.loads(payload)
if 'exp' in payload_data and time.time() > payload_data['exp']:
return False, "JWT has expired"
return True, "JWT is valid"
def refresh_jwt(token, secret_key, refresh_secret_key):
parts = token.split('.')
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'))
payload['exp'] = int(time.time()) + 86400 # 刷新过期时间,当前时间加上86400秒
new_token = generate_jwt({"typ": "JWT", "alg": "HS256"}, payload, refresh_secret_key)
return new_token
header = {
"typ": "JWT",
"alg": "HS256"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
secret_key = b"secret_key"
refresh_secret_key = b"refresh_secret_key"
# 生成JWT
jwt_token = generate_jwt(header, payload, secret_key).decode('utf-8')
print(f"生成的JWT: {jwt_token}")
# 验证JWT
is_valid, message = verify_jwt(jwt_token, secret_key)
print(message)
# 刷新JWT
new_jwt_token = refresh_jwt(jwt_token, secret_key, refresh_secret_key).decode('utf-8')
print(f"刷新后的JWT: {new_jwt_token}")
测试与调试
测试JWT通常需要模拟不同的场景,例如过期的JWT、非法的JWT等。确保在不同的场景下代码能够正确处理这些情况。下面是一个完整的测试流程和调试策略的示例:
import time
import hmac
import base64
import json
import hashlib
def generate_jwt(header, payload, secret_key):
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')).rstrip(b'=')
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')).rstrip(b'=')
message = header_encoded + b"." + payload_encoded
signature = hmac.new(secret_key, message, hashlib.sha256).digest()
signature_encoded = base64.urlsafe_b64encode(signature).rstrip(b'=')
return header_encoded + b"." + payload_encoded + b"." + signature_encoded
def verify_jwt(token, secret_key):
parts = token.split('.')
header = base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8')
payload = base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8')
signature = base64.urlsafe_b64decode(parts[2] + '===')
expected_signature = hmac.new(secret_key, base64.urlsafe_b64decode(parts[0] + '===').decode('utf-8') + "." + base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'), hashlib.sha256).digest()
if expected_signature != signature:
return False, "Signature verification failed"
payload_data = json.loads(payload)
if 'exp' in payload_data and time.time() > payload_data['exp']:
return False, "JWT has expired"
return True, "JWT is valid"
def refresh_jwt(token, secret_key, refresh_secret_key):
parts = token.split('.')
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '===').decode('utf-8'))
payload['exp'] = int(time.time()) + 86400 # 刷新过期时间,当前时间加上86400秒
new_token = generate_jwt({"typ": "JWT", "alg": "HS256"}, payload, refresh_secret_key)
return new_token
header = {
"typ": "JWT",
"alg": "HS256"
}
payload = {
"sub": "1234567890",
"name": "John Doe",
"admin": True,
"exp": int(time.time()) + 3600 # 过期时间,当前时间加上3600秒
}
secret_key = b"secret_key"
refresh_secret_key = b"refresh_secret_key"
# 生成JWT
jwt_token = generate_jwt(header, payload, secret_key).decode('utf-8')
print(f"生成的JWT: {jwt_token}")
# 验证JWT
is_valid, message = verify_jwt(jwt_token, secret_key)
print(message)
# 刷新JWT
new_jwt_token = refresh_jwt(jwt_token, secret_key, refresh_secret_key).decode('utf-8')
print(f"刷新后的JWT: {new_jwt_token}")
# 模拟过期JWT
time.sleep(3600) # 等待3600秒,JWT过期
is_valid, message = verify_jwt(jwt_token, secret_key)
print(message)
# 模拟非法JWT
invalid_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTYyNzU0NTYwMCJ9.d8sasasdsadasdasd"
is_valid, message = verify_jwt(invalid_jwt, secret_key)
print(message)
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章