CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
API 设计实战入门/课时/API 安全
RawBlame
课时目录12 节

API 设计实战入门

  1. 01API 基础
  2. 02HTTP 协议层
  3. 03REST 资源设计
  4. 04错误处理
  5. 05认证
  6. 06授权模型
  7. 07API 安全
  8. 08API 风格
  9. 09架构与集成
  10. 10测试
  11. 11可观测与合规
  12. 12文档与工具

API 安全

常见漏洞与最佳实践。

30 分钟

API 安全

这一课你将能做到

  1. 识别 OWASP API Security Top 10 风险,并在代码中修复常见漏洞
  2. 实现 SQL 注入、XSS、CSRF、SSRF 的防护方案
  3. 配置限流、防暴力破解、敏感数据脱敏等安全机制

为什么要学

API 是攻击者的主要入口。2023 年 OWASP 把 API 安全单独列为一个类别,因为传统 Web 安全的思路已经不够用了。你的课程数据、用户提交、支付信息——全靠 API 安全守着。一次注入、一次越权,可能让整个数据库泄露。

先建立图像

OWASP API Security Top 10 (2023):

1. 失效的对象级授权 (BOLA)     ← 越权访问他人数据
2. 失效的用户认证              ← Token 泄露、弱密码
3. 失效的对象属性级授权         ← 字段级越权(如看到他人手机号)
4. 资源消耗无限制              ← 无限制分页、大文件上传
5. 失效的功能级授权             ← 普通用户调用管理接口
6. 服务器端请求伪造 (SSRF)      ← 服务端被利用发恶意请求
7. 安全配置错误                ← 默认密码、调试模式开启
8. 注入                        ← SQL、NoSQL、命令注入
9. 库存管理不当                ← 旧版本 API 未下线、测试环境暴露
10. API 消费不安全             ← 调用第三方 API 时泄露数据

跟着做

步骤 1:防护 BOLA(越权访问)

BOLA 是最常见的 API 漏洞:用户 A 修改请求里的 ID,访问用户 B 的数据。

// 错误写法:直接按 ID 查询,不校验归属
@GetMapping("/submissions/{id}")
public ApiResponse<Submission> getSubmission(@PathVariable Long id) {
    return ApiResponse.success(submissionRepository.findById(id).orElseThrow());
}

// 正确写法:校验资源归属
@GetMapping("/submissions/{id}")
public ApiResponse<Submission> getSubmission(@PathVariable Long id,
                                             @AuthenticationPrincipal User currentUser) {
    Submission submission = submissionRepository.findById(id)
            .orElseThrow(() -> new BusinessException(404, "提交不存在"));

    if (!submission.getUserId().equals(currentUser.getId())
            && !currentUser.hasRole("ADMIN")) {
        throw new BusinessException(403, "无权查看此提交");
    }

    return ApiResponse.success(submission);
}

步骤 2:SQL 注入防护

// 错误写法:字符串拼接 SQL
@Query(value = "SELECT * FROM lessons WHERE title LIKE '%" + keyword + "%'", nativeQuery = true)
List<Lesson> searchByTitle(String keyword);

// 正确写法:参数化查询
@Query("SELECT l FROM Lesson l WHERE l.title LIKE %:keyword%")
List<Lesson> searchByTitle(@Param("keyword") String keyword);

// MyBatis 错误写法
@Select("SELECT * FROM lessons WHERE title LIKE '%${keyword}%'")
List<Lesson> searchByTitle(String keyword);

// MyBatis 正确写法
@Select("SELECT * FROM lessons WHERE title LIKE CONCAT('%', #{keyword}, '%')")
List<Lesson> searchByTitle(String keyword);

步骤 3:XSS 防护

// 输出时转义(Thymeleaf 自动转义,API 返回 JSON 时前端处理)
// 但存储时也要过滤危险内容

@Component
public class XssSanitizer {

    public String sanitize(String input) {
        if (input == null) return null;
        return input
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#x27;")
                .replace("/", "&#x2F;");
    }
}

// 或使用 OWASP Java Encoder
import org.owasp.encoder.Encode;

public String sanitize(String input) {
    return Encode.forHtml(input);
}

步骤 4:CSRF 防护

// API 服务通常用 JWT + Header 认证,天然免疫 CSRF
// 因为浏览器不会自动带 Authorization Header

// 如果必须用 Cookie:
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/public/**")
            );
        return http.build();
    }
}

步骤 5:SSRF 防护

// 错误写法:直接请求用户提供的 URL
@GetMapping("/proxy")
public String proxy(@RequestParam String url) {
    return restTemplate.getForObject(url, String.class);
}

// 正确写法:白名单 + 内网 IP 检测
@GetMapping("/proxy")
public String proxy(@RequestParam String url) {
    if (!isAllowedUrl(url)) {
        throw new BusinessException(400, "不允许的 URL");
    }
    return restTemplate.getForObject(url, String.class);
}

private boolean isAllowedUrl(String url) {
    try {
        URI uri = new URI(url);
        String host = uri.getHost();
        // 禁止内网 IP
        if (host == null || host.equals("localhost") || host.startsWith("127.")
                || host.startsWith("192.168.") || host.startsWith("10.")
                || host.startsWith("172.")) {
            return false;
        }
        // 白名单域名
        return allowedDomains.contains(host);
    } catch (URISyntaxException e) {
        return false;
    }
}

步骤 6:限流与防暴力破解

// 使用 Bucket4j + Redis 实现分布式限流
@Service
public class RateLimitService {

    private final RedisTemplate<String, String> redisTemplate;

    public boolean tryAcquire(String key, int limit, Duration duration) {
        String redisKey = "rate_limit:" + key;
        String current = redisTemplate.opsForValue().get(redisKey);

        if (current == null) {
            redisTemplate.opsForValue().set(redisKey, "1", duration);
            return true;
        }

        int count = Integer.parseInt(current);
        if (count >= limit) {
            return false;
        }

        redisTemplate.opsForValue().increment(redisKey);
        return true;
    }
}

// 登录接口限流:5 次/分钟
@PostMapping("/login")
public ApiResponse<TokenResponse> login(@RequestBody @Valid LoginRequest request,
                                       HttpServletRequest httpRequest) {
    String key = "login:" + request.getUsername();
    if (!rateLimitService.tryAcquire(key, 5, Duration.ofMinutes(1))) {
        throw new BusinessException(429, "尝试次数过多,请稍后再试");
    }
    // ... 登录逻辑
}

步骤 7:敏感数据脱敏

public class UserResponse {
    private Long id;
    private String username;

    @JsonSerialize(using = EmailSerializer.class)
    private String email;  // 显示为 a***@example.com

    @JsonSerialize(using = PhoneSerializer.class)
    private String phone;  // 显示为 138****5678
}

public class EmailSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException {
        if (value == null || !value.contains("@")) {
            gen.writeString(value);
            return;
        }
        String[] parts = value.split("@");
        String masked = parts[0].charAt(0) + "***@" + parts[1];
        gen.writeString(masked);
    }
}

讲清楚要点

  1. BOLA 是 API 头号威胁:任何按 ID 查询的接口都要校验「这个资源是不是当前用户的」。自动化扫描器专门扫这个。
  2. 参数化查询是 SQL 注入的唯一解:JPA/MyBatis 的 #{} 占位符是安全的,${} 字符串拼接是危险的。排序字段、表名等不能用 #{} 的场景,必须用白名单校验。
  3. XSS 防护要「输入过滤 + 输出编码」:存储时过滤 <script> 等标签,输出时根据上下文编码(HTML、JavaScript、URL)。
  4. CSRF 只影响 Cookie 认证:JWT + Header 的 API 不需要 CSRF Token,因为浏览器不会自动带 Authorization。
  5. 限流是最后一道防线:即使代码有漏洞,限流也能把损失控制在一定范围。按 IP、按用户、按接口多维度限流。

你可能会懵的地方

  • JWT 被窃取怎么办? 短有效期 + Refresh Token 轮换 + 绑定设备指纹 + 异常检测(异地登录提醒)。没有绝对安全,只有足够高的攻击成本。

  • API 需要防重放攻击吗? 金融级 API 需要。方案:请求带时间戳 + nonce,服务器校验时间戳在 5 分钟内、nonce 未使用过(Redis 存 nonce)。

  • 文件上传有什么风险? 上传可执行文件(.jsp、.php)导致 webshell;路径穿越(../../etc/passwd);大文件撑爆磁盘。防护:白名单后缀、重命名、限制大小、存对象存储而非本地。

  • GraphQL 有什么特殊安全问题? 深度嵌套查询导致 DoS(如嵌套 100 层); introspection 泄露 schema;批量查询绕过限流。需要限制查询深度、关闭 introspection、按复杂度限流。

  • 微服务之间调用需要认证吗? 需要。内网不是安全的,一旦一台机器被攻破,横向移动很容易。方案:服务间 mTLS、JWT 服务令牌、Kubernetes NetworkPolicy。

错法对照

错法问题正解
直接拼接 SQL 字符串SQL 注入参数化查询 / JPA / MyBatis #{}
返回用户密码哈希离线破解永不返回密码,包括哈希
不过滤文件上传类型webshell 上传白名单后缀 + 重命名 + 病毒扫描
错误信息返回堆栈泄露代码结构生产环境返回「系统繁忙」
所有接口不限流暴力破解、爬虫、DoS关键接口限流 + 验证码

卡点急救

  1. 被 WAF 拦截但不知道原因:WAF 规则可能误杀(如正常内容含 <script>)。查看 WAF 日志,调整规则或加白名单。
  2. HTTPS 证书错误:开发环境可用自签名证书,生产环境必须用 CA 签发证书。Let's Encrypt 免费。
  3. 限流影响正常用户:限流阈值设置过严。按「正常用户峰值 × 2」设置,同时提供验证码作为人机区分。

面试常问

  • OWASP API Top 10 里你最关注哪几个?怎么防护? 重点讲 BOLA(对象级授权校验)、注入(参数化查询)、认证失效(JWT 最佳实践)。能结合代码讲。

  • SQL 注入怎么防?MyBatis 的 #{} 和 ${} 区别? #{} 是预编译参数,安全;${} 是字符串替换,危险。排序字段、动态表名必须用白名单。

  • 怎么防止接口被爬虫抓? 限流(IP + 用户)、User-Agent 检测、行为分析(点击频率、鼠标轨迹)、验证码(关键操作)、数据水印(追踪泄露源头)。

自测三问

  1. 如果攻击者拿到一个用户的 JWT,能做什么?怎么把损失降到最低?
  2. 设计一个「导出用户数据」的接口,考虑哪些安全因素?
  3. 为什么 CSRF 对纯 API(无 Cookie)不构成威胁?

小结一句话

API 安全是「纵深防御」:认证授权是第一道门,输入校验是第二道,限流监控是第三道;OWASP Top 10 是必修课,BOLA 和注入是重灾区。

延伸

  • OWASP API Security Project
  • Spring Security 参考文档
  • 下一课:API 风格——REST、GraphQL、gRPC 与 WebSocket 选型。
加载本节练习…
← 上一课授权模型下一课 →API 风格