Java 程序员第 44 阶段10:大模型微服务拆分,独立服务解耦便于扩容维护,安全审计服务:敏感词过滤与合规检查独立化
- 大模型应用的安全合规挑战
- 安全审计服务的独立价值
- 敏感词过滤技术体系
- 语义级别的内容合规检测
- 输入输出双向审计
- 独立审计服务的架构设计
- Spring Boot 实现与实践
- 策略配置与动态规则管理
- 与网关、业务服务的集成
- 总结与展望
1.1 大模型不是安全的“黑箱”
很多企业引入大模型时,往往只关注效果,而忽视了安全合规。事实上,大模型应用的输入输出两端都存在显著风险。
输入端风险包括:用户可能通过提示词注入诱导模型输出有害内容;用户可能上传包含敏感信息、商业机密或违法内容的文本;竞争对手或恶意用户可能通过高频调用消耗资源或窃取知识。
输出端风险包括:模型可能生成歧视、暴力、色情、虚假信息等不合规内容;模型可能泄露训练数据中的隐私信息;模型可能输出错误的专业建议,导致业务风险或法律责任。
1.2 合规要求的多样性
不同行业、不同地区对大模型应用有不同的合规要求。数据安全要求个人信息、金融数据、医疗数据不能泄露;内容安全要求不得生成违法违规、低俗、歧视、仇恨言论;知识产权保护要求不得输出侵犯版权的内容;行业监管在金融、医疗、教育、政务等领域有专门的审查要求;企业内部合规则要求防止代码、商业机密、内部文档外泄。
这些要求无法靠大模型本身自动满足,必须在应用层建立统一的安全审计机制。
2.1 为什么安全审计不能耦合在业务服务中
如果把安全检测逻辑写在每个业务服务里,会面临规则更新困难、检测能力参差不齐、审计日志分散、重复开发等问题。敏感词库和策略模型升级时需要改所有业务服务,不同团队实现标准不一,容易漏检或误检,所有模型交互的输入输出无法统一追踪。
把安全审计拆分为独立服务,能够实现规则集中管理、检测能力复用、审计日志集中、策略动态更新、专业团队负责。
2.2 服务定位
安全审计服务应作为企业大模型应用的“安全闸门”,主要职责包括输入检测、输出检测、内容分类、脱敏处理、审计日志和策略管理。
3.1 传统敏感词匹配
最基础的方式是维护一个敏感词库,对文本进行匹配。使用 Trie 树可以高效实现中文敏感词匹配,时间复杂度接近 O(n)。
@Component
public class KeywordFilter {
private final TrieNode root = new TrieNode();
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.children.computeIfAbsent(c, k -> new TrieNode());
}
node.isEnd = true;
}
public List<String> detect(String text) {
List<String> result = new ArrayList<>();
for (int i = 0; i < text.length(); i++) {
TrieNode node = root;
int j = i;
StringBuilder matched = new StringBuilder();
while (j < text.length() && node.children.containsKey(text.charAt(j))) {
matched.append(text.charAt(j));
node = node.children.get(text.charAt(j));
if (node.isEnd) {
result.add(matched.toString());
}
j++;
}
}
return result;
}
}
3.2 变体与绕过识别
用户可能通过谐音、拆字、拼音、特殊符号等方式绕过敏感词过滤。规则引擎需要对这些变体进行归一化处理,包括同音字替换、繁简统一、去除无关符号、拼音汉字转换、形似字识别等。
3.3 正则与规则引擎
除了关键词,还可以使用正则表达式检测特定模式,例如身份证号、手机号、银行卡号、邮箱、URL 等。复杂规则可以使用规则引擎或轻量级脚本引擎管理。
3.4 动态词库管理
敏感词库需要支持动态更新。通过管理后台增删改敏感词,词库变化后热加载到内存,按租户或业务线维护不同词库,并支持白名单避免误杀。
4.1 关键词过滤的局限性
关键词过滤速度快、实现简单,但也有很多局限。它无法识别语义层面的违规内容,例如隐喻、讽刺、上下文相关的恶意表达;容易产生误报,例如医学、法律、新闻场景中的敏感词可能是正常表述;对长篇内容、多轮对话的整体风险难以评估。
因此,必须引入语义级别的检测。
4.2 基于大模型的内容审核
可以使用专门的内容审核模型对文本进行多维度分类,包括违法违规、色情低俗、歧视仇恨、虚假信息、个人隐私、商业机密、代码泄露等风险维度。
国内大模型厂商通常提供内容审核 API,可以直接接入。也可以基于开源文本分类模型在私有数据上微调。
4.3 双模型审核策略
对于高风险场景,可以采用双模型审核。轻量级规则引擎做第一轮快速过滤,大模型做第二轮语义级深度审核。两者结果不一致时,进入人工复核或更严格的处理流程。
4.4 幻觉与事实性检测
大模型可能一本正经地胡说八道。安全审计服务还可以集成事实性核查、来源引用检查和置信度评估,对关键事实进行 RAG 检索验证,要求模型输出引用来源,并对不确定性进行标注。
5.1 输入审计
输入审计发生在用户请求进入大模型调用链路之前。主要检测敏感词和隐私信息、识别提示词注入攻击、判断请求意图是否属于高风险类别、对敏感信息进行脱敏或拦截。
对于提示词注入,常见的防御手段包括输入转义、结构隔离、语义检测和输出约束。
5.2 输出审计
输出审计发生在大模型返回结果之后。主要检测输出中的敏感词和违规内容、是否包含企业内部敏感信息、对高风险输出进行打标或拦截、对可公开内容直接放行。
5.3 审计日志结构
public class AuditLog {
private String id;
private String requestId;
private String tenantId;
private String userId;
private String businessCode;
private String direction;
private String content;
private List<DetectionResult> detections;
private String finalAction;
private String riskLevel;
private LocalDateTime createdAt;
private Map<String, Object> metadata;
}
public class DetectionResult {
private String type;
private String ruleId;
private String description;
private String riskLevel;
private List<String> matchedFragments;
}
6.1 整体架构
在整体大模型微服务架构中,安全审计服务应位于流量入口或网关之后,对所有进入大模型调用链路的请求和从模型返回的响应进行统一检测。
业务服务 / 网关
│
▼
┌─────────────────┐
│ 安全审计服务 │
│ 输入 · 输出 · 合规│
└────────┬────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
关键词引擎 语义审核模型 审计日志库
Trie / AC 分类模型 PostgreSQL / ES
6.2 检测流水线
一次完整的安全审计通常分为多个阶段:
- 格式标准化:把请求或响应中的文本内容提取出来。
- 预处理:繁简转换、去重、去噪、归一化。
- 关键词检测:Trie 树快速匹配敏感词和正则模式。
- 语义检测:调用分类模型或内容审核 API。
- 风险聚合:综合各检测结果,给出最终风险等级和处理动作。
- 日志记录:把检测详情写入审计日志。
6.3 处理动作
根据检测结果,安全审计服务可以采取不同的处理动作:
- PASS:无风险,直接放行。
- MASK:对敏感信息做脱敏处理,例如把身份证号替换为星号。
- BLOCK:高风险内容,直接拦截并返回提示。
- REVIEW:中风险内容,放行但标记为待人工复核。
- LOG:仅记录,不拦截,用于后续分析。
7.1 项目结构
security-audit-service/
├── api/
│ └── AuditController.java
├── application/
│ ├── AuditService.java
│ ├── KeywordDetectionService.java
│ └── SemanticDetectionService.java
├── domain/
│ ├── AuditLog.java
│ └── DetectionResult.java
└── infrastructure/
├── KeywordFilter.java
├── ContentReviewClient.java
└── AuditLogRepository.java
7.2 核心服务实现
@Service
public class AuditService {
private final KeywordDetectionService keywordService;
private final SemanticDetectionService semanticService;
private final AuditLogRepository auditLogRepository;
private final MaskingService maskingService;
public AuditResponse audit(AuditRequest request) {
long start = System.currentTimeMillis();
List<DetectionResult> detections = new ArrayList<>();
detections.addAll(keywordService.detect(request.getContent()));
detections.addAll(semanticService.detect(request.getContent()));
String riskLevel = aggregateRisk(detections);
String action = decideAction(request, riskLevel, detections);
String finalContent = request.getContent();
if ("MASK".equals(action)) {
finalContent = maskingService.mask(request.getContent(), detections);
}
AuditLog log = AuditLog.builder()
.id(UUID.randomUUID().toString())
.requestId(request.getRequestId())
.tenantId(request.getTenantId())
.userId(request.getUserId())
.businessCode(request.getBusinessCode())
.direction(request.getDirection())
.content(request.getContent())
.detections(detections)
.finalAction(action)
.riskLevel(riskLevel)
.elapsedMs(System.currentTimeMillis() - start)
.createdAt(LocalDateTime.now())
.build();
auditLogRepository.save(log);
return AuditResponse.builder()
.requestId(request.getRequestId())
.action(action)
.riskLevel(riskLevel)
.content(finalContent)
.detections(detections)
.build();
}
private String aggregateRisk(List<DetectionResult> detections) {
if (detections.stream().anyMatch(d -> "HIGH".equals(d.getRiskLevel()))) {
return "HIGH";
}
if (detections.stream().anyMatch(d -> "MEDIUM".equals(d.getRiskLevel()))) {
return "MEDIUM";
}
return "LOW";
}
private String decideAction(AuditRequest request, String riskLevel,
List<DetectionResult> detections) {
if ("HIGH".equals(riskLevel)) {
return "BLOCK";
}
if (detections.stream().anyMatch(d -> "PII".equals(d.getType()))) {
return "MASK";
}
if ("MEDIUM".equals(riskLevel)) {
return "REVIEW";
}
return "PASS";
}
}
7.3 控制器接口
@RestController
@RequestMapping("/api/v1/audit")
public class AuditController {
private final AuditService auditService;
@PostMapping("/input")
public ResponseEntity<AuditResponse> auditInput(@RequestBody @Valid AuditRequest request) {
request.setDirection("INPUT");
return ResponseEntity.ok(auditService.audit(request));
}
@PostMapping("/output")
public ResponseEntity<AuditResponse> auditOutput(@RequestBody @Valid AuditRequest request) {
request.setDirection("OUTPUT");
return ResponseEntity.ok(auditService.audit(request));
}
@GetMapping("/logs")
public ResponseEntity<Page<AuditLog>> queryLogs(AuditQuery query, Pageable pageable) {
return ResponseEntity.ok(auditService.queryLogs(query, pageable));
}
}
8.1 规则模型
public class AuditRule {
private String id;
private String tenantId;
private String businessCode;
private String type; // KEYWORD / REGEX / SEMANTIC
private String riskLevel; // LOW / MEDIUM / HIGH
private String action; // PASS / MASK / BLOCK / REVIEW
private String pattern;
private String description;
private boolean enabled;
private int priority;
}
8.2 动态规则加载
规则可以存储在数据库中,服务定期刷新内存中的规则:
@Scheduled(fixedRate = 60000)
public void refreshRules() {
List<AuditRule> rules = ruleRepository.findAllEnabled();
this.ruleCache = rules;
this.keywordFilter.rebuild(rules.stream()
.filter(r -> "KEYWORD".equals(r.getType()))
.map(AuditRule::getPattern)
.collect(Collectors.toList()));
}
8.3 灰度策略
新规则上线时,可以先在部分租户或业务上灰度:
- 仅记录不拦截,观察误报率。
- 逐步扩大覆盖范围。
- 确认无误后再全量启用 BLOCK 或 MASK。
9.1 集成到网关
大模型路由网关是集成安全审计服务的最佳位置。所有进入大模型的请求和从模型返回的响应都会经过网关,网关可以在转发前后调用审计服务。
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return auditInput(exchange)
.flatMap(inputResult -> {
if ("BLOCK".equals(inputResult.getAction())) {
return writeBlockedResponse(exchange, inputResult);
}
return chain.filter(exchange)
.then(auditOutput(exchange));
});
}
9.2 集成到业务服务
对于不走统一网关的内部服务,可以直接引入审计客户端 SDK:
@Component
public class AuditClient {
private final WebClient webClient;
public Mono<AuditResponse> auditInput(String content, String requestId) {
return webClient.post()
.uri("/api/v1/audit/input")
.bodyValue(AuditRequest.builder()
.requestId(requestId)
.content(content)
.direction("INPUT")
.build())
.retrieve()
.bodyToMono(AuditResponse.class);
}
}
9.3 异步审计与同步审计
- 同步审计:在请求处理链路中同步调用,适用于必须拦截的场景。会带来一定延迟,需控制超时。
- 异步审计:把内容投递到消息队列,审计服务消费后记录。适用于对延迟敏感、事后追责的场景。
安全审计服务是大模型应用不可或缺的“安全守门员”。通过独立部署,企业能够统一管理敏感词库、内容审核策略、合规规则和审计日志,避免安全能力散落在各业务服务中。
Java 程序员在实现安全审计服务时,应重点关注:
- 关键词检测与语义检测的结合,平衡检测速度与准确性。
- 输入输出双向审计,覆盖全链路风险。
- 动态规则管理,支持热更新和灰度发布。
- 与网关、业务服务的灵活集成方式。
- 审计日志的持久化、查询与合规留存。
至此,本阶段的五篇文章从 RAG 检索、知识库管理、对话会话、大模型路由网关到安全审计,系统梳理了大模型微服务拆分中的关键独立服务。每一篇文章都围绕“独立服务解耦便于扩容维护”的核心理念,给出了架构思考、Spring Boot 实现示例和生产实践建议。希望这套方法论能够帮助 Java 程序员在构建企业级大模型应用时,做出更加稳健、可扩展的架构决策。