Access Token与Refresh Token
person c猿人
watch_later 2024-09-18 21:20:48
visibility 596
class Refresh Token
bookmark 分享
在前后端分离的项目中,Token 认证通常采用 JWT(JSON Web Token)机制来实现用户的身份认证。为了确保用户体验良好、安全性高,并且避免用户频繁登录,需要合理设计 Token 的时效及更新机制。
常见 Token 方案及时效设计
前后端分离的项目通常会使用两个 Token:
- 访问 Token(Access Token):用于用户的每次请求,通常有效时间较短,确保安全。
- 刷新 Token(Refresh Token):用于刷新访问 Token,通常有效时间较长,避免用户频繁登录。
1. 访问 Token(Access Token)
- 有效期:一般为 15 分钟到 1 小时之间。过期后不可再使用,需要通过刷新 Token 获取新的访问 Token。
- 用途:每次请求时在
Authorization头部携带访问 Token 进行身份认证。 - 存储位置:通常保存在 浏览器的内存 或 前端状态管理工具(如 Vuex, Redux) 中,避免长期保存带来的安全问题。
2. 刷新 Token(Refresh Token)
- 有效期:通常为几天或几周,具体根据业务需要决定。
- 用途:当访问 Token 过期时,通过刷新 Token 获取新的访问 Token,避免用户重新登录。
- 存储位置:可以存放在 HTTP-Only Cookie 中,这样能减少 XSS 攻击的风险。
实现方案
Token 时效机制的具体实现流程:
-
用户登录时生成两个 Token:
- 用户通过用户名和密码进行登录,服务器验证成功后,生成一个短期有效的 访问 Token 和一个长期有效的 刷新 Token,并将它们返回给客户端。
-
客户端存储 Token:
- 访问 Token 存储在内存或短期存储(如 Vuex),有效期内随每个请求通过
Authorization头部传递给后端。 - 刷新 Token 存储在 HTTP-Only Cookie 中,浏览器不能直接访问,增加安全性。
- 访问 Token 存储在内存或短期存储(如 Vuex),有效期内随每个请求通过
-
使用访问 Token 进行身份验证:
- 每次前端请求时,前端从内存中提取访问 Token,并将其放入请求头部
Authorization: Bearer <token>中发送给后端。 - 后端解密 Token,验证 Token 的有效性。
- 每次前端请求时,前端从内存中提取访问 Token,并将其放入请求头部
-
访问 Token 过期时:
- 当后端发现访问 Token 过期时,返回 401 错误码。
- 前端捕获到 401 错误时,自动携带刷新 Token 向服务器发送请求,获取新的访问 Token。
-
刷新 Token 更新访问 Token:
- 后端接收到前端传来的刷新 Token,并验证其有效性。
- 若刷新 Token 有效,后端重新生成一个新的访问 Token 返回给前端。
- 前端更新内存中的访问 Token,继续进行后续操作。
-
刷新 Token 过期处理:
- 如果刷新 Token 也过期,则向前端返回 401 错误,并要求用户重新登录。
- 前端跳转到登录页面,提示用户重新进行身份验证。
Token 刷新机制的时序图
+-------------+ +-------------+ +--------------------+
| Client | | Backend | | Authentication Flow |
+-------------+ +-------------+ +--------------------+
| | |
| Login Request | |
| ------------------------> | |
| | Verify User |
| | ------------------------> |
| | |
| | Return Access & Refresh |
| <------------------------| Token |
| | |
| Store Access Token in | |
| memory, Refresh Token | |
| in HTTP-Only Cookie | |
| | |
| Send API Requests with | |
| Access Token | |
| ------------------------> | Verify Access Token |
| | |
| | |
| | [Access Token Expired] |
| | <-------------------------|
| | |
| [Request New Access | |
| Token using Refresh Token]| |
| ------------------------> | Verify Refresh Token |
| | |
| | Generate New Access Token |
| | <-------------------------|
| | |
| [Update Access Token] | |
| | |
| [Refresh Token Expired] | |
| <-------------------------| Return 401, Re-login |
| | |
前端代码示例(Vue.js + Axios)
下面是一个实现 Token 自动刷新机制 的 Vue.js 和 Axios 配置代码:
import axios from 'axios';
// 创建 Axios 实例
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
// 请求拦截器,携带 Access Token
api.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem('accessToken'); // 或从 Vuex 中获取
if (accessToken) {
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器,处理 Token 过期的情况
api.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = getCookie('refreshToken'); // 从 HTTP-Only Cookie 获取 Refresh Token
if (refreshToken) {
try {
const { data } = await api.post('/auth/refresh-token', { refreshToken });
localStorage.setItem('accessToken', data.accessToken); // 更新 Access Token
originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`;
return api(originalRequest); // 重新发起原始请求
} catch (refreshError) {
console.log('Refresh token expired, please re-login');
// 跳转到登录页
window.location.href = '/login';
}
}
}
return Promise.reject(error);
}
);
export default api;
后端代码示例(Node.js + Express + JWT)
以下是使用 jsonwebtoken 来生成和验证 Token 的示例:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const ACCESS_TOKEN_SECRET = 'your-access-token-secret';
const REFRESH_TOKEN_SECRET = 'your-refresh-token-secret';
let refreshTokens = []; // 存储有效的刷新 Token
// 登录接口
app.post('/login', (req, res) => {
const username = req.body.username;
// 验证用户身份,略...
const accessToken = jwt.sign({ username }, ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ username }, REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
refreshTokens.push(refreshToken);
res.json({
accessToken,
refreshToken,
});
});
// 刷新 Token 接口
app.post('/auth/refresh-token', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken || !refreshTokens.includes(refreshToken)) {
return res.sendStatus(403);
}
jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
const accessToken = jwt.sign({ username: user.username }, ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
res.json({ accessToken });
});
});
// 保护路由示例
app.get('/protected', authenticateToken, (req, res) => {
res.send('This is a protected route');
});
// 中间件验证 Access Token
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
刷新 Token 的过期处理
- 前端:在捕获到刷新 Token 过期的情况下,前端应清除本地存储的 Token 并跳转到登录页面。
- 后端:刷新 Token 过期后,后端应返回 401 错误码并记录下可能的异常。
总结
- 使用 访问 Token 短期内进行认证,使用 刷新 Token 定期获取新的访问 Token。
- 刷新 Token 存储在安全的 HTTP-Only Cookie 中,
避免 XSS 攻击。
- 通过 401 状态码和拦截器 自动处理 Token 的刷新和过期处理。
chat评论区
评论列表
每天进步一点点
whatshot热门文章