本文详细介绍了JWT单点登录教程,从JWT的基本概念和工作原理入手,逐步讲解了如何使用JWT实现单点登录的具体步骤,包括生成和验证JWT令牌的过程。此外,文章还提供了示例代码,帮助读者更好地理解和实践JWT单点登录。
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它是一种紧凑、轻量级的认证机制。JWT 通过使用 JSON 对象来携带声明,这些声明可以被发送到各方并在各个应用之间传递。JWT 的工作方式是通过将声明编码为 JSON 对象并使用加密签名来确保数据的完整性和真实性。
JWT 的工作流程通常包括以下几个步骤:
单点登录(Single Sign-On,SSO)是指用户只需要在一个地方进行一次登录,就可以访问多个受保护的应用或系统,而不需要在每个应用或系统中单独登录。
单点登录有多种实现方式,常见的包括使用 Cookie、OAuth、SAML(Security Assertion Markup Language)和 JWT 等技术。JWT 由于其携带信息的灵活性和无状态性,是实现单点登录的理想选择。
// 假设有一个全局的用户认证服务 const authenticateUser = (username, password) => { // 用户认证逻辑 const user = checkUsernameAndPassword(username, password); if (user) { // 使用 JWT 生成令牌 const token = jwt.sign( { id: user.id, username: user.username, scope: user.scope }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return token; } return null; }; // 用户登录时调用此函数 const token = authenticateUser('user1', 'password1');
在使用 JWT 实现单点登录之前,需要进行一些准备工作:
jsonwebtoken
和 express
)。// 安装依赖 npm install express jsonwebtoken
生成 JWT 令牌时,通常需要三个部分:头部(Header)、载荷(Payload)和签名(Signature)。
const jwt = require('jsonwebtoken'); const token = jwt.sign( { // 载荷部分 id: '123', username: 'user1', scope: 'admin' }, process.env.JWT_SECRET, // 密钥 { // 签名选项 expiresIn: '1h' // 令牌有效时间 } ); console.log(token);
在收到 JWT 令牌后,需要验证其有效性。这通常涉及到解码 JWT 并检查签名是否有效。
jwt.verify( token, // 传入的令牌 process.env.JWT_SECRET, // 密钥 (err, decoded) => { if (err) { // 验证失败 console.log('Token verification failed:', err); } else { // 验证成功 console.log('Token decoded:', decoded); } } );
在实际应用中,通常需要通过前端存储 JWT 令牌,并在每次请求时自动携带该令牌。后端服务需要检查 JWT 令牌的有效性,并根据其内容执行相应的操作。
const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.post('/login', (req, res) => { const { username, password } = req.body; // 模拟用户验证过程 if (username === 'admin' && password === 'password') { const token = jwt.sign( { id: '123', username: 'admin', scope: 'admin' }, process.env.JWT_SECRET, { expiresIn: '1h' } ); res.json({ token }); } else { res.status(400).json({ message: 'Invalid credentials' }); } }); app.get('/protected', (req, res) => { const token = req.headers['authorization']; if (!token) { return res.status(401).send({ auth: false, message: 'No token provided.' }); } jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' }); } res.status(200).json({ auth: true, message: 'Authentication successful.', user: decoded }); }); }); app.use((req, res, next) => { const token = req.headers['authorization']; if (token) { jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(401).json({ auth: false, message: 'Failed to authenticate token.' }); } // 设置中间件,将解析的用户信息附加到请求对象 req.user = decoded; next(); }); } else { res.status(401).json({ auth: false, message: 'No token provided.' }); } }); app.get('/profile', (req, res) => { res.status(200).json({ user: req.user }); });
本示例将使用 Node.js 和 Express 框架。
创建用户注册和登录接口,使用 JWT 来生成和验证令牌。
const express = require('express'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcrypt'); const expressValidator = require('express-validator'); const User = require('./models/User'); // 假设有一个用户模型 const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(expressValidator()); app.post('/register', (req, res) => { const { username, password } = req.body; // 验证输入 req.checkBody('username', 'Username is required').notEmpty(); req.checkBody('password', 'Password is required').notEmpty(); const errors = req.validationErrors(); if (errors) { return res.status(400).json({ message: errors[0].msg }); } // 检查用户名是否已存在 User.findOne({ where: { username } }) .then(user => { if (user) { return res.status(400).json({ message: 'Username already exists' }); } // 创建新用户 bcrypt.hash(password, 10, (err, hash) => { if (err) { return res.status(500).json({ message: 'Error hashing password' }); } User.create({ username: username, password: hash }) .then(user => { const token = jwt.sign( { id: user.id, username: user.username, scope: user.scope }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return res.status(200).json({ token }); }) .catch(err => res.status(500).json({ message: err.message })); }); }) .catch(err => res.status(500).json({ message: err.message })); }); app.post('/login', (req, res) => { const { username, password } = req.body; // 验证输入 req.checkBody('username', 'Username is required').notEmpty(); req.checkBody('password', 'Password is required').notEmpty(); const errors = req.validationErrors(); if (errors) { return res.status(400).json({ message: errors[0].msg }); } // 查找用户 User.findOne({ where: { username } }) .then(user => { if (!user) { return res.status(401).json({ message: 'Invalid credentials' }); } // 验证密码 bcrypt.compare(password, user.password, (err, isMatch) => { if (err) throw err; if (isMatch) { const token = jwt.sign( { id: user.id, username: user.username, scope: user.scope }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return res.status(200).json({ token }); } else { return res.status(401).json({ message: 'Invalid credentials' }); } }); }) .catch(err => res.status(500).json({ message: err.message })); });
创建 /login
接口生成 JWT 令牌,并在 /protected
接口验证 JWT 令牌。
app.post('/login', (req, res) => { const { username, password } = req.body; // 验证输入 req.checkBody('username', 'Username is required').notEmpty(); req.checkBody('password', 'Password is required').notEmpty(); const errors = req.validationErrors(); if (errors) { return res.status(400).json({ message: errors[0].msg }); } // 查找用户 User.findOne({ where: { username } }) .then(user => { if (!user) { return res.status(401).json({ message: 'Invalid credentials' }); } // 验证密码 bcrypt.compare(password, user.password, (err, isMatch) => { if (err) throw err; if (isMatch) { const token = jwt.sign( { id: user.id, username: user.username, scope: user.scope }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return res.status(200).json({ token }); } else { return res.status(401).json({ message: 'Invalid credentials' }); } }); }) .catch(err => res.status(500).json({ message: err.message })); }); app.get('/protected', (req, res) => { const token = req.headers['authorization']; if (!token) { return res.status(401).send({ auth: false, message: 'No token provided.' }); } jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' }); } res.status(200).json({ auth: true, message: 'Authentication successful.', user: decoded }); }); });
确保每次请求都携带 JWT 令牌,并在后端验证其有效性。
app.use((req, res, next) => { const token = req.headers['authorization']; if (token) { jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(401).json({ auth: false, message: 'Failed to authenticate token.' }); } // 设置中间件,将解析的用户信息附加到请求对象 req.user = decoded; next(); }); } else { res.status(401).json({ auth: false, message: 'No token provided.' }); } }); app.get('/profile', (req, res) => { res.status(200).json({ user: req.user }); });
当 JWT 令牌过期后,用户需要重新登录或通过刷新令牌来获取新的 JWT 令牌。
app.post('/refresh', (req, res) => { const token = req.body.token; jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { return res.status(401).json({ message: 'Token is invalid or has expired.' }); } const newToken = jwt.sign( { id: decoded.id, username: decoded.username, scope: decoded.scope }, process.env.JWT_SECRET, { expiresIn: '1h' } ); return res.status(200).json({ token: newToken }); }); });
使用 CORS(跨域资源共享)中间件来处理跨域请求。
const cors = require('cors'); app.use(cors({ origin: '*', credentials: true }));
JWT 单点登录是一种高效、安全的认证方式,适用于多种应用场景。它通过保持无状态性来减少服务器负载,同时通过加密签名确保数据的安全性。