CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
API 安全
这一课你将能做到
识别 OWASP API Security Top 10 风险,并在代码中修复常见漏洞
实现 SQL 注入、XSS、CSRF、SSRF 的防护方案
配置限流、防暴力破解、敏感数据脱敏等安全机制
为什么要学
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("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("/", "/");
}
}
// 或使用 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);
}
}
讲清楚要点
BOLA 是 API 头号威胁 :任何按 ID 查询的接口都要校验「这个资源是不是当前用户的」。自动化扫描器专门扫这个。
参数化查询是 SQL 注入的唯一解 :JPA/MyBatis 的 #{} 占位符是安全的,${} 字符串拼接是危险的。排序字段、表名等不能用 #{} 的场景,必须用白名单校验。
XSS 防护要「输入过滤 + 输出编码」 :存储时过滤 <script> 等标签,输出时根据上下文编码(HTML、JavaScript、URL)。
CSRF 只影响 Cookie 认证 :JWT + Header 的 API 不需要 CSRF Token,因为浏览器不会自动带 Authorization。
限流是最后一道防线 :即使代码有漏洞,限流也能把损失控制在一定范围。按 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 关键接口限流 + 验证码
卡点急救
被 WAF 拦截但不知道原因 :WAF 规则可能误杀(如正常内容含 <script>)。查看 WAF 日志,调整规则或加白名单。
HTTPS 证书错误 :开发环境可用自签名证书,生产环境必须用 CA 签发证书。Let's Encrypt 免费。
限流影响正常用户 :限流阈值设置过严。按「正常用户峰值 × 2」设置,同时提供验证码作为人机区分。
面试常问
OWASP API Top 10 里你最关注哪几个?怎么防护?
重点讲 BOLA(对象级授权校验)、注入(参数化查询)、认证失效(JWT 最佳实践)。能结合代码讲。
SQL 注入怎么防?MyBatis 的 #{} 和 ${} 区别?
#{} 是预编译参数,安全;${} 是字符串替换,危险。排序字段、动态表名必须用白名单。
怎么防止接口被爬虫抓?
限流(IP + 用户)、User-Agent 检测、行为分析(点击频率、鼠标轨迹)、验证码(关键操作)、数据水印(追踪泄露源头)。
自测三问
如果攻击者拿到一个用户的 JWT,能做什么?怎么把损失降到最低?
设计一个「导出用户数据」的接口,考虑哪些安全因素?
为什么 CSRF 对纯 API(无 Cookie)不构成威胁?
小结一句话 API 安全是「纵深防御」:认证授权是第一道门,输入校验是第二道,限流监控是第三道;OWASP Top 10 是必修课,BOLA 和注入是重灾区。
延伸