接口未授权如何校验

wen 网络安全 24

本文目录导读:

接口未授权如何校验

  1. 校验的本质:谁来访问?是否有权限?
  2. 常见的技术实现方案
  3. 权限校验(细粒度授权)
  4. 最佳实践与安全建议

接口未授权(即未通过身份认证或权限不足)是Web安全中常见的问题,校验的核心目标是识别未登录用户权限越级用户,并拒绝其访问。

以下是不同场景下接口未授权的校验方法、技术实现和最佳实践:

校验的本质:谁来访问?是否有权限?

在开始技术实现前,需要明确两个维度:

  1. 身份认证(Authentication):你是谁?(是否已登录?Token是否有效?)
  2. 授权(Authorization):你能做什么?(普通用户能否访问管理员接口?)

“未授权”通常包括:

  • 401 Unauthorized:未提供身份凭证或凭证无效(严格来说是“未认证”)。
  • 403 Forbidden:已认证,但权限不足(用户A试图删除用户B的数据)。

常见的技术实现方案

基于 Session/Cookie 的传统校验

适用于传统的服务端渲染应用或前后端同源架构。

  • 校验流程

    1. 用户登录后,服务器创建 Session,将 Session ID 写入 Cookie。
    2. 客户端发起请求时,自动携带 Cookie。
    3. 服务器校验:从请求中获取 Session ID -> 查 Session 存储(内存/Redis) -> 判断用户是否在线。
  • 示例代码(Node.js + Express + express-session)

    // 登录成功后
    req.session.userId = user.id;
    req.session.role = user.role;
    // 校验中间件
    function requireAuth(req, res, next) {
        if (req.session.userId) {
            next(); // 已登录,放行
        } else {
            res.status(401).json({ error: '未登录,请先登录' });
        }
    }
    // 应用到需要登录的接口
    app.get('/user/profile', requireAuth, (req, res) => {
        // ... 处理业务
    });

基于 Token(JWT)的无状态校验

前后端分离(SPA)或移动端应用的主流方案

  • 校验流程

    1. 用户登录后,服务器生成 JWT Token(包含用户ID、角色、过期时间)。
    2. 客户端将 Token 存储在 localStoragecookie(需设置 httpOnly 防XSS)等。
    3. 客户端发起请求时,在 HTTP Header 中添加 Authorization: Bearer <token>
    4. 服务器校验:从 Header 获取 Token -> 验证签名(防篡改) -> 解析 Payload -> 检查过期时间。
  • 示例代码(Node.js + jsonwebtoken)

    const jwt = require('jsonwebtoken');
    const SECRET_KEY = 'your-256-bit-secret'; // 实际应存在环境变量
    function authenticateToken(req, res, next) {
        const authHeader = req.headers['authorization'];
        const token = authHeader && authHeader.split(' ')[1]; // 取Bearer后的部分
        if (!token) {
            return res.status(401).json({ error: '缺少访问令牌' });
        }
        jwt.verify(token, SECRET_KEY, (err, user) => {
            if (err) {
                // 常见错误:过期、签名不匹配
                if (err.name === 'TokenExpiredError') {
                    return res.status(401).json({ error: '令牌已过期,请重新登录' });
                }
                return res.status(403).json({ error: '无效的令牌' });
            }
            req.user = user; // 将解析出的用户信息挂载到请求对象上
            next();
        });
    }
    // 受保护的路由
    app.get('/api/orders', authenticateToken, (req, res) => {
        // req.user 包含用户ID和角色
        // 可以进一步判断 req.user.role 是否为 'admin'
        res.json({ orders: [] });
    });

基于 OAuth2.0 或 OpenID Connect 的第三方校验

适用于需要第三方登录(如微信、Google)或开放的 API 平台。

  • 校验流程
    1. 用户通过第三方认证后,获取 Access Token(和可能的 ID Token)。
    2. 客户端将 Access Token 发送给后端。
    3. 服务器校验:后端拿着 Access Token 去第三方服务(如微信、Google)的 tokeninfouserinfo 接口验证真伪和有效性。
  • 核心关注点:不要信任客户端传来的任何用户信息(如用户ID、邮箱),后端必须主动向授权服务器验证 Token 的有效性。

基于 API Key 的校验

适用于服务器之间的通信(内部微服务)或公开 API(如天气API)。

  • 校验流程

    1. 注册获得固定 API Key(通常是长字符串)。
    2. 请求时放在 Header(X-API-Key)或 Query 参数(?api_key=xxx)。
    3. 服务器校验:拿收到的 API Key 与数据库中存储的 Key 比较。
  • 示例代码(Python Flask)

    from flask import Flask, request, abort, jsonify
    app = Flask(__name__)
    VALID_API_KEYS = {'key1', 'key2'} # 实际应用应查数据库
    def require_api_key(func):
        def wrapper(*args, **kwargs):
            api_key = request.headers.get('X-API-Key')
            if api_key not in VALID_API_KEYS:
                abort(401, description='无效的 API Key')
            return func(*args, **kwargs)
        return wrapper
    @app.route('/api/data')
    @require_api_key
    def get_data():
        return jsonify({'data': 'secret data'})

权限校验(细粒度授权)

身份认证通过后,还需判断是否有权执行具体操作,常见模型:

基于角色(RBAC)

用户拥有一个角色(如 adminuser),角色关联一组权限。

function requireRole(...roles) {
    return (req, res, next) => {
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ error: '权限不足' });
        }
        next();
    }
}
// 使用
app.delete('/api/admin/users/:id', authenticateToken, requireRole('admin'), deleteUserHandler);

基于资源归属

用户只能操作属于自己的资源。

app.get('/api/orders/:orderId', authenticateToken, (req, res) => {
    const order = findOrderById(req.params.orderId);
    if (order.userId !== req.user.id && req.user.role !== 'admin') {
        return res.status(403).json({ error: '无权访问此订单' });
    }
    res.json(order);
});

基于访问控制列表(ACL)

更精细的权限控制,可以直接存储“用户ID -> 资源ID -> 权限”。

def check_acl(user_id, resource_type, resource_id, action):
    # 查询数据库:user_id 是否对 resource_id 拥有 action 权限
    return db.query("...").exists()
# 在接口中调用

最佳实践与安全建议

  1. 统一拦截:不要在每个接口里重复写校验逻辑,而是使用全局中间件(Middleware)或过滤器(Filter)统一处理。
  2. 明确返回状态码
    • 401:身份认证失败(未登录、Token过期、签名错误)。
    • 403:权限不足(已登录但账号被封、角色无权限、资源不归属)。
    • 不要返回“未找到”或“参数错误”:对未授权请求返回 404 可能会暴露资源是否存在,增加安全风险?不一定,但最好统一返回 403 并给出明确提示。
  3. 防范常见攻击
    • Token 泄露:使用 HTTPS;JWT 不要存放敏感信息(如密码);合理设置过期时间(Access Token 短时,Refresh Token 长时)。
    • CSRF:如果使用 Cookie + Session,需增加 CSRF Token 校验(或设置 SameSite=Strict)。
    • 权限提升:后端不要依赖前端传来的角色信息(如 req.body.role = 'admin'),必须从数据库或 Token 中读取。
  4. 测试场景:编写自动化测试覆盖:
    • 不携带 Token 访问受保护接口 -> 期望 401。
    • 携带过期/伪造 Token 访问 -> 期望 401/403。
    • 以普通用户身份尝试删除管理员数据 -> 期望 403。
  5. 日志与监控:记录失败的认证和授权请求(包括客户端IP、时间、试图访问的资源),用于异常检测。
场景 推荐方案 关键验证点
前后端分离(SPA/App) JWT(存于 Authorization Header) 签名、过期时间、用户角色
传统 Web 应用 Session(存于 Cookie,httpOnly Session 是否存在、是否过期
第三方登录 OAuth2.0 / OpenID 后端主动向授权服务器验证 Token
内部服务调用 API Key / mTLS Key 是否在允许列表中、IP 白名单
细粒度权限 RBAC / 资源归属校验 角色、资源所属用户 ID

核心原则:永远不要在客户端做权限判断,所有敏感操作必须在服务端进行二次校验。

抱歉,评论功能暂时关闭!