本文目录导读:

- 核心两步走:认证(你是谁)+ 授权(你能做什么)
- 基于 Token 的校验流程(最常用,JWT 为例)
- 基于 Session/Cookie 的校验(传统服务端渲染)
- 三种常见的未授权漏洞及防范
- 更好的实践方案
- 总结一张表
接口未授权校验是Web安全中最基础也最重要的环节,就是在允许用户访问某个接口或执行某个操作之前,系统必须确认“你是谁”以及“你有没有权限做这件事”。
如果校验不严,攻击者可以直接访问敏感数据或执行管理操作,下面我来介绍校验方法、实现方式以及常见误区。
核心两步走:认证(你是谁)+ 授权(你能做什么)
未授权校验通常分两个阶段:
- 身份认证:确认用户身份,常见方式有 Session/Cookie、JWT(JSON Web Token)、OAuth2、API Key。
- 权限校验:确认用户有权限,常见模型有 RBAC(基于角色的权限控制)、ABAC(基于属性的权限控制)。
基于 Token 的校验流程(最常用,JWT 为例)
这是目前前后端分离项目的标准做法。
流程图:
用户登录 -> 服务端验证账号密码 -> 生成JWT并签名 -> 返回给客户端 -> 客户端存储(localStorage/Header) -> 每次请求携带Token(Authorization: Bearer <token>) -> 服务端拦截器/中间件校验Token签名和有效期 -> 解析出用户ID和角色 -> 进入业务逻辑前进行权限判断 -> 通过则执行,失败则返回401/403
核心校验代码实现(Node.js Express 中间件为例):
const jwt = require('jsonwebtoken');
// 1. Token 验证中间件(身份认证)
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (token == null) {
return res.status(401).json({ error: '未提供认证令牌' }); // 401 Unauthorized
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
// 常见错误:Token过期、签名不匹配、Token被篡改
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: '令牌已过期' });
}
return res.status(403).json({ error: '无效的令牌' }); // 403 Forbidden
}
// 将解析出的用户信息挂载到请求对象上,供后续使用
req.user = user;
next(); // 继续执行后续中间件或路由
});
}
// 2. 权限校验中间件(授权)
function requireRole(role) {
return (req, res, next) => {
// 这里假设Token中包含了用户角色信息,由authenticateToken解析后的user对象提供
if (!req.user || req.user.role !== role) {
return res.status(403).json({ error: `需要[${role}]角色权限` }); // 403 Forbidden
}
next();
};
}
// 3. 在路由中使用
// 公开接口:无需校验
app.get('/api/public', (req, res) => { res.send('公开数据'); });
// 需登录接口:只校验Token
app.get('/api/profile', authenticateToken, (req, res) => {
res.send(`你好, ${req.user.name}`);
});
// 管理员接口:校验Token + 校验角色
app.delete('/api/users/:id', authenticateToken, requireRole('admin'), (req, res) => {
// 只有admin才能删除用户
res.send('用户已删除');
});
状态码规范:
- 401 Unauthorized:未提供Token或Token无效/过期,客户端应重新登录。
- 403 Forbidden:已认证但无权限,客户端应提示无访问权限,而不是重新登录。
基于 Session/Cookie 的校验(传统服务端渲染)
流程: 用户登录 -> 服务端创建Session并存储(内存/Redis) -> 下发Session ID到Cookie -> 客户端请求自动携带Cookie -> 服务端中间件根据Cookie找Session -> 判断是否登录 -> 判断角色权限。
关键点: Session ID 必须随机且足够复杂(防猜测),使用 HttpOnly、Secure、SameSite 属性保护 Cookie。
三种常见的未授权漏洞及防范
IDOR(不安全的直接对象引用)
- 漏洞示例:访问
/api/order/12345就能看到别人的订单。 - 根因:只校验了“是否登录”,没校验“这个订单是否属于当前用户”。
- 防范:必须校验资源归属,在代码中增加
if (req.user.id !== order.userId)的判断。
未校验用户状态(如“权限变更”)
- 漏洞示例:用户登录后,管理员后台将其降权或封禁,但该用户当前有效的Token仍能访问管理员API,直到Token过期。
- 防范:
- 短Token + 刷新机制(推荐):Token过期时间短(15分钟),每次用Refresh Token刷新,每次刷新时检查用户最新状态。
- 黑名单:在Redis中维护一个Token黑名单,每次请求查询。
缺少对“操作”类型的校验(垂直越权)
- 漏洞示例:普通用户通过修改请求方法(如GET改DELETE)或参数来访问管理接口。
- 防范:明确接口的HTTP方法(GET/查询、POST/创建、DELETE/删除)和角色要求。
更好的实践方案
- 统一校验框架:不把校验逻辑散落在每个控制器里,而是使用拦截器(Interceptor)或中间件统一处理。
- 白名单机制:默认所有接口都是“需要认证”的,只有显式声明的公开接口才放行。
- 最小权限原则:用户/服务只被授予完成其任务所需的最小权限,上传文件的接口应只校验文件类型,而不能同时允许删除服务器文件。
- 日志与监控:记录所有未授权访问尝试(特别是403),分析模式以发现可能的攻击行为。
- 安全第三方库:使用成熟框架和库,Spring Security(Java)、ASP.NET Core Identity、Passport.js(Node)、Django REST Framework(Python)。
总结一张表
| 校验层面 | 检测点 | 返回码 | 典型处理 |
|---|---|---|---|
| 是否携带凭证 | Header有无Token,Cookie有无Session ID | 401 | 提示“请先登录” |
| 凭证是否有效 | Token签名、是否过期、是否被篡改 | 401 | 提示“登录已过期,请重新登录” |
| 凭证对应的身份是否被禁用 | 用户状态(是否被踢下线、封号) | 403 | 提示“您的账号已被禁用” |
| 是否有权执行该操作 | 角色是否匹配、资源是否属于自己 | 403 | 提示“您没有权限执行此操作” |
最后记住两点:
- 永远不要在客户端做权限校验。(不能让前端隐藏一个按钮就以为后端安全了)
- 所有未授权校验失败,最终都要落到两个状态码上:401表示“你是谁”有问题,403表示“你能做什么”有问题。