跳到主要内容

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 是必须的
  • 推荐访问令牌设置短时间,通过刷新令牌更新的设计

参考链接