JWT单点登录学习涉及JWT的基础概念、工作原理及其在单点登录中的应用。本文详细介绍了JWT的生成、验证和刷新过程,并提供了在不同框架中实现JWT单点登录的示例代码。通过这些内容,读者可以深入了解JWT技术及其在构建安全的单点登录系统中的应用。
JWT基础概念介绍JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在双方之间安全地传输信息。JWT的设计初衷是为了提供一种安全的方式,用于在不同系统之间传递认证信息。这种令牌是自包含的,不需要服务器端保留任何状态信息,使得它非常适合用于分布式系统和微服务架构。
JWT是一种紧凑、自包含的令牌,用于在网络上传递信息。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
头部(Header)
头部通常包含两部分信息:令牌的类型(typ)和所使用的签名算法(alg)。例如,HS256
表示使用HMAC-SHA-256算法进行签名。
{ "typ": "JWT", "alg": "HS256" }
载荷(Payload)
载荷包含声明(Claims),这些声明是对主体的声明,可以是公开的声明(如:iss
、exp
等)或私有声明。标准中定义了许多载荷字段,但也可以自定义用途的字段。
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
签名(Signature)
签名部分基于头部和载荷生成,用于验证消息的完整性。生成签名的步骤如下:
base64Url
编码。.
和载荷。例如,如果使用HMAC SHA-256算法,签名的计算方式如下:
import hmac import hashlib import base64 import time # 假设密钥为 'secret' secret = 'secret' # 头部和载荷 header = base64.urlsafe_b64encode(b'{"typ":"JWT","alg":"HS256"}').decode("utf-8") payload = base64.urlsafe_b64encode(b'{"sub":"1234567890","name":"John Doe","iat":1516239022}').decode("utf-8") # 拼接头部和载荷 message = header + '.' + payload # 生成签名 signature = hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest() signature = base64.urlsafe_b64encode(signature).decode("utf-8") # 最终的JWT jwt_token = message + '.' + signature
优势:
应用场景:
单点登录(Single Sign-On,SSO)是一种身份验证方法,允许用户使用一组凭证(如用户名和密码)登录一个系统,然后可以访问其他多个系统,而无需再次进行身份验证。SSO系统可以显著提高用户体验,减少重复登录的过程。
SSO的实现方式有很多种,常见的包括:
JWT非常适合用于构建SSO系统,因为它具有无状态性和安全性。以下是如何利用JWT实现SSO的基本步骤:
生成JWT令牌需要包含以下步骤:
jsonwebtoken
库。npm install jsonwebtoken
const jwt = require('jsonwebtoken'); const secret = 'my_secret_key'; const token = jwt.sign({ sub: '1234567890', name: 'John Doe', iat: Math.floor(Date.now() / 1000) }, secret, { algorithm: 'HS256' }); console.log(token);
验证JWT令牌需要以下步骤:
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] }); console.log(decoded);
令牌的存储和刷新是保证用户体验和安全性的关键步骤:
const refreshSecret = 'my_refresh_secret'; const refreshToken = jwt.sign({ sub: '1234567890', name: 'John Doe', iat: Math.floor(Date.now() / 1000) }, refreshSecret, { algorithm: 'HS256', expiresIn: '1d' // 有效时间为1天 }); // 刷新令牌 const newToken = jwt.sign({ sub: '1234567890', name: 'John Doe', iat: Math.floor(Date.now() / 1000) }, secret, { algorithm: 'HS256' }); console.log(newToken);JWT单点登录的实战演练
安装依赖
npm install express jsonwebtoken
服务器端代码
const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); const secret = 'my_secret_key'; app.post('/login', (req, res) => { const user = { id: 1, name: 'John Doe' }; const token = jwt.sign(user, secret, { expiresIn: '1h' // 有效时间为1小时 }); res.json({ token }); }); app.get('/protected', (req, res) => { const token = req.headers.authorization.split(' ')[1]; jwt.verify(token, secret, (err, decoded) => { if (err) { return res.status(401).json({ message: 'Unauthorized' }); } res.json({ user: decoded }); }); }); app.listen(3000, () => { console.log('Server running on port 3000'); });
客户端代码
const fetch = require('node-fetch'); fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'john.doe', password: 'password123' }) }).then((response) => response.json()).then((data) => { console.log(data.token); fetch('/protected', { headers: { 'Authorization': 'Bearer ' + data.token } }).then((response) => response.json()).then((data) => { console.log(data.user); }); });
添加依赖
在pom.xml
中添加JWT依赖:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
服务器端代码
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.*; import java.util.Date; @SpringBootApplication public class JwtDemoApplication { public static void main(String[] args) { SpringApplication.run(JwtDemoApplication.class, args); } @RestController public class AuthController { private final String secret = "my_secret_key"; @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) { if (isValidUser(username, password)) { return createToken(username); } return "Unauthorized"; } private boolean isValidUser(String username, String password) { // 实际应用中应从数据库等存储中验证用户 return username.equals("john.doe") && password.equals("password123"); } private String createToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, secret) .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 有效时间为1小时 .compact(); } @GetMapping("/protected") public String protectedResource(@RequestHeader("Authorization") String authorization) { String token = authorization.replace("Bearer ", ""); String username = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); if (username != null && !username.isEmpty()) { return "Hello, " + username; } return "Unauthorized"; } } }
客户端代码
const fetch = require('node-fetch'); fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'username=john.doe&password=password123' }).then((response) => response.json()).then((data) => { console.log(data); fetch('/protected', { headers: { 'Authorization': 'Bearer ' + data } }).then((response) => response.text()).then((data) => { console.log(data); }); });
安装依赖
pip install djangorestframework rest_framework_jwt
服务器端代码
from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_jwt.settings import api_settings from django.contrib.auth.models import User jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class ObtainAuthToken(APIView): def post(self, request): username = request.data.get('username') password = request.data.get('password') if username and password: try: user = User.objects.get(username=username) if user.check_password(password): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return Response({'token': token}) else: return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) class ProtectedResource(APIView): def get(self, request): token = request.META.get('HTTP_AUTHORIZATION', '').split(' ')[1] payload = api_settings.JWT_DECODE_HANDLER(token) user = User.objects.get(id=payload['user_id']) return Response({'user': user.username})
客户端代码
const fetch = require('node-fetch'); fetch('/api/auth/login/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'john.doe', password: 'password123' }) }).then((response) => response.json()).then((data) => { console.log(data.token); fetch('/api/protected/', { headers: { 'Authorization': 'JWT ' + data.token } }).then((response) => response.json()).then((data) => { console.log(data.user); }); });JWT单点登录的安全性分析
JWT的签名机制是确保令牌完整性和防止篡改的关键。签名通过使用密钥(如HMAC-SHA256)来验证令牌的内容是否被修改。如果密钥丢失或泄露,任何人都可以伪造令牌,因此密钥的安全管理至关重要。
当令牌过期时,客户端需要重新获取新的令牌。一种常见的方式是使用刷新令牌(Refresh Token)。
刷新令牌流程
// 刷新令牌逻辑 const refreshToken = 'refresh_token'; fetch('/refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: refreshToken }) }).then((response) => response.json()).then((data) => { console.log(data.newToken); });
令牌泄露可能导致恶意用户访问受保护的资源。为了减少泄露风险,可以采取以下措施:
通过以上介绍和示例代码,你可以了解更多关于JWT单点登录的实现细节和最佳实践。希望这些示例能帮助你更好地理解和应用JWT技术。