本文详细介绍了JWT单点登录的概念、工作原理及其实现步骤,帮助读者深入了解JWT和单点登录技术。文章通过具体示例展示了如何使用JWT实现单点登录,并提供了部署和测试的方法。通过学习,读者可以掌握JWT单点登录的实践技巧,并了解如何处理令牌过期和安全存储等问题。jwt单点登录学习涵盖了从基础理论到实战应用的全过程。
JWT (JSON Web Token) 是一种开放标准 (RFC 7519),用于在网络应用间安全地将信息作为JSON对象传输。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。这三个部分通过一个.
分隔符连接起来,形成最终的字符串。
当用户请求登录时,服务器验证用户信息,并生成JWT。生成的JWT由用户客户端保存,之后每次用户向服务端发起请求时,都会将JWT包含在HTTP请求头中的Authorization
字段里。服务器收到请求后,从Authorization
字段中获取JWT,对JWT进行验证,验证通过则允许访问,否则返回错误信息。
单点登录(Single Sign-On, SSO)是一种身份验证方法,它允许用户使用一组凭证(如用户名和密码)登录多个系统或服务而无需重复验证。单点登录的主要目标是简化用户的身份验证过程,提供更安全和便捷的用户体验。
通过实现单点登录,可以提升用户对系统或服务的信任度,同时降低系统管理员的工作负担。此外,单点登录还能提高系统的安全性,防止因用户频繁输入密码产生的各种安全问题。
首先,需要确保服务端支持JWT认证,并且客户端能够发送和接收JWT。以下是一些准备工作:
jsonwebtoken
(Node.js)。在服务端,可以使用中间件来处理JWT请求,确保请求携带有效的JWT。下面是一个简单的示例,使用Node.js和Express来完成这一任务。
const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); const secret = 'your_secret_key'; // 用于签名的密钥 app.use(express.json()); // 用于解析JSON请求体 // 定义一个中间件来验证JWT function verifyToken(req, res, next) { const token = req.headers['authorization']; if (!token) { return res.status(401).json({ message: 'No token provided' }); } jwt.verify(token, secret, (err, decoded) => { if (err) { return res.status(403).json({ message: 'Failed to authenticate token' }); } req.decoded = decoded; next(); }); } // 确保token包含在授权头中 app.use('/protected', verifyToken); // 示例路由 app.post('/login', (req, res) => { const user = { id: '123', username: 'test', }; const token = jwt.sign(user, secret, { expiresIn: '1h' }); // 签发一个有效期为1小时的JWT res.json({ token }); }); app.listen(3000, () => console.log('JWT server running on port 3000'));
服务端需要实现用户的登录逻辑,并生成JWT。在上述示例中,/login
路由处理用户的登录请求,并返回一个JWT用于后续请求的认证。
客户端需要保存JWT,并在每次请求时发送JWT。下面是一个简单的示例,使用JavaScript来完成这一任务。
const fetch = require('node-fetch'); const login = async () => { const response = await fetch('http://localhost:3000/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'test', password: 'password' }) }); const data = await response.json(); const token = data.token; localStorage.setItem('token', token); // 保存JWT到localStorage return token; }; const protectedRoute = async () => { const token = localStorage.getItem('token'); // 从localStorage读取JWT const response = await fetch('http://localhost:3000/protected', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); console.log(await response.json()); }; login().then(() => { protectedRoute(); });
假设我们有一个包含两个微服务的应用系统:serviceA
和serviceB
。用户需要登录到serviceA
,并使用相同的凭证访问serviceB
。我们将使用JWT来实现单点登录。
首先,我们编写服务端代码来处理登录请求并生成JWT。
const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); const secret = 'your_secret_key'; // 用于签名的密钥 app.use(express.json()); // 用于解析JSON请求体 // 定义一个中间件来验证JWT function verifyToken(req, res, next) { const token = req.headers['authorization']; if (!token) { return res.status(401).json({ message: 'No token provided' }); } jwt.verify(token, secret, (err, decoded) => { if (err) { return res.status(403).json({ message: 'Failed to authenticate token' }); } req.decoded = decoded; next(); }); } // 示例路由 app.post('/login', (req, res) => { const user = { id: '123', username: 'test', }; const token = jwt.sign(user, secret, { expiresIn: '1h' }); // 签发一个有效期为1小时的JWT res.json({ token }); }); app.use('/protected', verifyToken); app.get('/protected', (req, res) => { res.json({ message: 'Protected resource accessed', userId: req.decoded.id }); }); app.listen(3000, () => console.log('JWT server running on port 3000'));
接下来,我们编写客户端代码来处理登录请求并保存JWT。
const fetch = require('node-fetch'); const login = async () => { const response = await fetch('http://localhost:3000/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'test', password: 'password' }) }); const data = await response.json(); const token = data.token; localStorage.setItem('token', token); // 保存JWT到localStorage return token; }; const protectedRoute = async () => { const token = localStorage.getItem('token'); // 从localStorage读取JWT const response = await fetch('http://localhost:3000/protected', { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); console.log(await response.json()); }; login().then(() => { protectedRoute(); }); `` #### 4.3 部署和测试 部署上述代码后,可以通过浏览器或Postman等工具进行测试。首先登录到`serviceA`,获取JWT并保存。然后使用该JWT访问`serviceB`,验证单点登录是否成功。以下是具体的部署和测试步骤: ```javascript // 部署服务器 const server = app.listen(3000, () => { console.log('JWT server running on port 3000'); }); // 测试服务器 login().then(() => { protectedRoute().then(response => { console.log('Test successful:', response); server.close(); }); });
JWT通常包含一个过期时间,当令牌过期时,需要刷新令牌。可以通过设置刷新令牌,或在请求中携带额外的刷新令牌来实现刷新。下面是一个刷新令牌的简单示例:
app.post('/refresh', (req, res) => { const refreshToken = req.body.refreshToken; jwt.verify(refreshToken, secret, (err, decoded) => { if (err) return res.status(403).json({ message: 'Failed to authenticate token' }); const token = jwt.sign(decoded, secret, { expiresIn: '1h' }); res.json({ token }); }); });
客户端在令牌过期时,调用/refresh
接口获取新的令牌。以下是客户端刷新令牌的具体示例:
const refreshToken = async () => { const response = await fetch('http://localhost:3000/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: localStorage.getItem('refreshToken') }) }); const data = await response.json(); const token = data.token; localStorage.setItem('token', token); // 保存新的JWT到localStorage };
JWT需要安全存储,以防止泄露。可以将JWT存储在浏览器的localStorage
或sessionStorage
中,但要注意这些存储方式相对不安全,建议使用HTTPOnly
的cookie
来存储JWT。
// 使用cookie存储JWT app.post('/login', (req, res) => { const user = { id: '123', username: 'test', }; const token = jwt.sign(user, secret, { expiresIn: '1h' }); res.cookie('token', token, { httpOnly: true }); res.json({ message: 'Login successful' }); });
当服务端和客户端跨域运行时,需要设置适当的CORS(跨域资源共享)头。以下是一个简单的CORS中间件示例:
const cors = require('cors'); app.use(cors({ origin: 'http://localhost:8080', // 客户端运行的域名 credentials: true, })); app.options('*', cors()); // 预检请求
通过使用JWT实现单点登录,可以简化用户的登录过程,提高系统的安全性。JWT通过携带必要的用户信息和签名,确保了信息的安全传输。同时,JWT的无状态特性使其非常适合分布式系统中的认证需求。