安全与鉴权
大约 3 分钟
第十四章:安全与认证
14.1 API密钥管理:不要把“钥匙”放在代码里
最常见、也最危险的错误:
- 把 API Key 写进
application.properties - 把 Key 提交到 Git
- 在日志里打印 Key
推荐做法:
- 本地开发:环境变量(
OPENAI_API_KEY/QWEN_API_KEY/DEEPSEEK_API_KEY) - 生产环境:KMS/Secrets Manager(K8s Secret、云密钥服务)
- 定期轮换:Key 过期/泄露时可以快速替换
14.2 认证集成:先鉴权再调用模型
不管你的 AI 接口看起来多简单,只要放到公网,就会有人刷:
- 刷配额导致你成本爆炸
- 用你的 Key 做“免费代理”
- 用提示词注入诱导模型做越权工具调用
因此建议你至少做到两件事:
- 所有 AI API 都需要鉴权(哪怕是内部系统)
- 为不同用户/租户设置额度(QPS、并发、每日 token 上限)
14.2.1 最小 API Key 鉴权(可复制运行)
下面示例使用 Spring Security 的 Filter 做一个最小鉴权:请求头 X-API-Key 必须匹配配置项。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>application.yml
app:
api:
key: ${APP_API_KEY:dev-key}过滤器
package com.example.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class ApiKeyAuthFilter extends OncePerRequestFilter {
private final String expected;
public ApiKeyAuthFilter(AppSecurityProperties properties) {
this.expected = properties.apiKey();
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String key = request.getHeader("X-API-Key");
if (expected != null && !expected.isBlank() && !expected.equals(key)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"success\":false,\"error\":\"Unauthorized\"}");
return;
}
filterChain.doFilter(request, response);
}
}配置绑定
package com.example.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app.api")
public record AppSecurityProperties(String key) {
public String apiKey() {
return key == null ? "" : key;
}
}安全配置
package com.example.security;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableConfigurationProperties(AppSecurityProperties.class)
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, ApiKeyAuthFilter apiKeyAuthFilter) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(registry -> registry
.requestMatchers("/actuator/**").denyAll()
.anyRequest().permitAll()
)
.addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class)
.httpBasic(Customizer.withDefaults())
.build();
}
}这个方案很“朴素”,但非常适合小团队第一版上线:先挡住最明显的风险,再逐步演进到 OAuth2/JWT/网关鉴权。
14.3 权限控制:工具调用必须做业务鉴权
当你引入函数调用/工具集成后,权限控制的重点会变化:
- 模型只是“提议”调用工具
- 真正执行的是你的后端
因此你必须做到:
- 工具执行前做权限校验:当前用户是否允许查询该订单/访问该资源
- 工具参数校验:防止越权/注入/超长输入导致的资源消耗攻击
- 审计日志:记录调用链路,便于追责与排查
14.4 安全最佳实践清单
- 输入限制:最大长度、最大消息数、禁止可疑指令(按业务需要)
- 输出治理:敏感信息脱敏,必要时做内容安全检测
- 成本防护:限流、并发控制、配额、异常告警
- 供应商隔离:不同租户使用不同 Key,避免“串号” 点击这里👇🏻获取:100万QPS短链系统、复杂的商城微服务系统、智能翻译助手AI Agent、SaaS点餐系统、刷题吧小程序、商城系统、秒杀系统、AI项目、代码生成神器、苏三demo项目、智能天气播报AI Agent、智能代码审查AI Agent等 10 个项目的:项目源代码、开发教程和技术答疑
