Bearer 认证
Bearer 认证是一种使用 HTTP Authorization 头进行基于令牌的认证机制。
什么是 Bearer 认证
Bearer 认证是 RFC 6750 中定义的在 HTTP 请求中发送 OAuth 2.0 访问令牌的标准方法。
基本机制
- 客户端从认证服务器获取访问令牌
- 在后续的 API 请求中,将该令牌包含在 Authorization 头中发送
- 服务器验证令牌并处理请求
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"Bearer" 名称的由来
"Bearer" 意为"持有者"或"携带者"。它基于这样的概念:持有(=Bearer)此令牌的人可以行使与该令牌相关联的权限。
令牌格式
JWT(JSON Web Token)
JWT 是最常用的 Bearer 令牌格式。
JWT 结构:
头部.载荷.签名
实例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
解码后:
头部:
{
"alg": "HS256",
"typ": "JWT"
}
载荷:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
不透明令牌(Opaque Token)
与 JWT 不同,令牌本身不包含信息,仅在服务器端具有意义的令牌。
d8f7a3b2c1e9f4d6a5b8c7e2f1a9d3b4
- 作为令牌 ID 或会话 ID 在数据库中管理
- 要了解令牌内容,需要向授权服务器查询
实现方法
客户端实现
JavaScript(Fetch API)
async function fetchUserProfile(accessToken) {
const response = await fetch('https://api.example.com/users/me', {
method: 'GET',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to fetch user profile');
}
return await response.json();
}
使用 Axios 的示例
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
});
// 使用拦截器自动添加令牌
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 401 错误时自动刷新
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post(
'https://auth.example.com/token',
{
grant_type: 'refresh_token',
refresh_token: refreshToken,
}
);
const { access_token } = response.data;
localStorage.setItem('accessToken', access_token);
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return api(originalRequest);
} catch (refreshError) {
// 刷新失败时登出
localStorage.clear();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
服务器端实现
Node.js + Express + JWT
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const SECRET_KEY = process.env.JWT_SECRET;
// 认证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
}
// 受保护的端点
app.get('/api/users/me', authenticateToken, (req, res) => {
res.json({
id: req.user.sub,
name: req.user.name,
email: req.user.email,
});
});
// 令牌发放端点
app.post('/auth/login', async (req, res) => {
const { username, password } = req.body;
// 用户认证(实际应在数据库中验证)
const user = await authenticateUser(username, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 生成访问令牌
const accessToken = jwt.sign(
{
sub: user.id,
name: user.name,
email: user.email,
},
SECRET_KEY,
{ expiresIn: '15m' } // 15分钟
);
// 生成刷新令牌
const refreshToken = jwt.sign(
{ sub: user.id },
SECRET_KEY,
{ expiresIn: '7d' } // 7天
);
res.json({
access_token: accessToken,
refresh_token: refreshToken,
token_type: 'Bearer',
expires_in: 900, // 秒
});
});
ASP.NET Core
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// JWT 认证设置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
)
};
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// 受保护的端点
app.MapGet("/api/users/me", (HttpContext context) =>
{
var userId = context.User.FindFirst("sub")?.Value;
var userName = context.User.FindFirst("name")?.Value;
return Results.Ok(new { id = userId, name = userName });
})
.RequireAuthorization();
app.Run();
安全考虑
令牌的存储位置
❌ 存储在 LocalStorage(不推荐)
// 容易受到 XSS 攻击
localStorage.setItem('accessToken', token);
问题:
- 可以通过 JavaScript 访问,因此很容易被 XSS 攻击窃取
- 恶意脚本可以读取令牌并发送到外部
✅ 保存在内存中(推荐)
// 使用闭包或 React Context 管理
let accessToken = null;
function setToken(token) {
accessToken = token;
}
function getToken() {
return accessToken;
}
优点:
- 降低 XSS 攻击风险
- 页面重新加载时令牌会丢失(提高安全性)
缺点:
- 每次页面重新加载都需要重新登录
- 需要管理刷新令牌
HTTPS 必须使用
由于 Bearer 令牌以明文发送,因此必须使用 HTTPS。
// 在生产环境中强制使用 HTTPS
if (process.env.NODE_ENV === 'production' && req.protocol !== 'https') {
return res.redirect(`https://${req.hostname}${req.url}`);
}
令牌的有效期
- 访问令牌:短时间(5-30分钟)
- 刷新令牌:长时间(几天到几周)
// 访问令牌:15分钟
const accessToken = jwt.sign(payload, SECRET, { expiresIn: '15m' });
// 刷新令牌:7天
const refreshToken = jwt.sign({ sub: userId }, SECRET, { expiresIn: '7d' });
CORS 配置
app.use(cors({
origin: 'https://yourapp.com',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
}));
令牌黑名单
实现在登出或令牌泄露时使令牌无效的机制。
const blacklistedTokens = new Set();
function invalidateToken(token) {
blacklistedTokens.add(token);
// 建议保存到外部存储,如 Redis
}
function authenticateToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (blacklistedTokens.has(token)) {
return res.status(401).json({ error: 'Token has been revoked' });
}
// 正常的令牌验证处理
jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
}
Bearer 认证的优缺点
优点
-
无状态
- 服务器端无需会话管理
- 可扩展性高
-
标准化
- OAuth 2.0 的标准规范
- 许多库和框架都支持
-
灵活性
- 适用于微服务之间的认证
- 易于在移动应用和 SPA 中使用
-
跨域支持
- 不易受 CORS 限制
缺点
-
XSS 攻击风险
- 如果存储在 JavaScript 可访问的位置会很危险
-
令牌大小
- JWT 相对较大,每次请求都会产生开销
-
难以使令牌无效
- 由于是无状态的,很难立即使令牌无效
- 需要实现黑名单
-
有效期管理
- 需要刷新令牌机制
总结
- Bearer 认证是使用 HTTP Authorization 头的基于令牌的认证
- JWT 是最常见的令牌格式
- 为了防止 XSS 攻击,令牌应在内存中管理,而不是 LocalStorage
- HTTPS 是必须的
- 推荐访问令牌设置短时间,通过刷新令牌更新的设计