跳到主要内容

Bearer 认证

Bearer 认证是一种使用 HTTP Authorization 头进行基于令牌的认证机制。

什么是 Bearer 认证​

Bearer 认证是 RFC 6750 中定义的在 HTTP 请求中发送 OAuth 2.0 访问令牌的标准方法。

基本机制​

  1. 客户端从认证服务器获取访问令牌
  2. 在后续的 API 请求中,将该令牌包含在 Authorization 头中发送
  3. 服务器验证令牌并处理请求
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 认证的优缺点​

优点​

  1. 无状态

    • 服务器端无需会话管理
    • 可扩展性高
  2. 标准化

    • OAuth 2.0 的标准规范
    • 许多库和框架都支持
  3. 灵活性

    • 适用于微服务之间的认证
    • 易于在移动应用和 SPA 中使用
  4. 跨域支持

    • 不易受 CORS 限制

缺点​

  1. XSS 攻击风险

    • 如果存储在 JavaScript 可访问的位置会很危险
  2. 令牌大小

    • JWT 相对较大,每次请求都会产生开销
  3. 难以使令牌无效

    • 由于是无状态的,很难立即使令牌无效
    • 需要实现黑名单
  4. 有效期管理

    • 需要刷新令牌机制

总结​

  • Bearer 认证是使用 HTTP Authorization 头的基于令牌的认证
  • JWT 是最常见的令牌格式
  • 为了防止 XSS 攻击,令牌应在内存中管理,而不是 LocalStorage
  • HTTPS 是必须的
  • 推荐访问令牌设置短时间,通过刷新令牌更新的设计

参考链接​