第 16 篇:AI 系统过不了等保?企业上线必做!敏感词 + 权限 + 审计日志全套方案(Java+AI 落地实战系列 | 直接上生产 | 杜绝翻车)
本文是《Java+AI 落地实战 从入门到生产级》系列第 16 篇
往期回顾:
-
第1篇:Java程序员学AI/转AI全指南,从CURD到AI应用工程师(零算法,4周落地)
-
第2篇:Java 后端转 AI,这条完整链路 90% 的人没搞懂!AI 怎么自动干活、Java转AI链路:LLM、RAG、FunctionCall、ToolCall、Skills、Agent、MCP
-
第3篇:别再焦虑了!Java开发做AI,根本不用学Python
-
第4篇:10分钟跑通!SpringBoot对接通义千问,实现AI对话功能
-
第5篇:SpringBoot实现流式输出,和ChatGPT体验一模一样
-
第6篇:对话记忆怎么实现?Java版多轮上下文对话完整方案
-
第7篇:从零搭建本地RAG知识库,内网可用,零API费用
-
第8篇:支持PDF/Word/Excel!Java实现多格式文档自动解析入库
-
第9篇:别再用内存向量库了!Milvus向量数据库Java对接全指南
-
第10篇:RAG检索准确率太低?5个优化技巧,准确率提升50%
-
第11篇:多租户权限隔离!企业级知识库部门级权限管控方案
-
第12篇:一键对接企业微信!把知识库装进企业微信,全员开箱即用
-
第13篇:Java 对接通义千问大模型,性能优化全攻略
-
第14篇:AI 只会聊天?教你千问点奶茶能力的底层技术
-
第 15 篇:企业级 AI 接口怎么设计?限流、降级、熔断全套方案-涨薪30%的手段
上一篇我们基于 Sentinel 完成AI接口高可用架构,保障高峰期系统稳定、防止服务雪崩。很多小伙伴做完稳定性改造之后卡在最后一关:企业内部部署AI知识库,合规不达标,无法正式上线、过不了等保测评。
用户输入违规话术、AI输出敏感内容、员工越权查看其他部门文档、所有交互没有操作记录,任意一个问题都会成为上线拦路虎。今天落地一套端到端AI系统合规防护体系:输入输出双重敏感词校验、三级细粒度权限隔离、全链路操作审计日志,整套代码无缝接入我们前面搭建的RAG知识库项目,满足等保2.0标准,拿来就能用于企业项目验收。
先看合规改造前后的核心差异:
一、AI知识库落地,等保测评4项硬性合规要求
企业内部AI系统、RAG知识库,等保评审重点核查4大维度,也是我们整套方案的实现目标:
下面沿着请求流转链路逐层实现,形成完整合规闭环。
二、核心合规 1:输入输出双重敏感词过滤
内容安全第一道防线,遵循「违规内容进不来、敏感信息出不去」原则:不仅拦截用户输入,AI返回结果同样需要过滤。
技术选型:DFA确定有穷自动机
不推荐简单 contains 字符串匹配。DFA字典树算法性能极强,支持上万条敏感词库毫秒级匹配,不会拖慢问答接口响应,适配我们之前搭建的高并发AI问答服务。
@Component
public class SensitiveWordFilter {
private final Map<String, Object> dfaMap = new HashMap<>();
/**
* 项目启动初始化敏感词字典树
*/
@PostConstruct
public void init() {
// 生产环境:从数据库/配置文件动态加载敏感词,支持后台管理刷新词库
List<String> sensitiveWords = SensitiveWordHolder.getAllSensitiveWords();
for (String word : sensitiveWords) {
addWord(word);
}
}
/**
* 构建DFA字典树
*/
private void addWord(String word) {
Map<String, Object> currentMap = dfaMap;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
Object nextMap = currentMap.get(String.valueOf(c));
if (nextMap == null) {
Map<String, Object> newMap = new HashMap<>();
newMap.put("isEnd", false);
currentMap.put(String.valueOf(c), newMap);
currentMap = newMap;
} else {
currentMap = (Map<String, Object>) nextMap;
}
if (i == word.length() - 1) {
currentMap.put("isEnd", true);
}
}
}
/**
* 检测文本,返回命中全部敏感词
*/
public List<String> checkSensitiveWord(String text) {
List<String> result = new ArrayList<>();
for (int i = 0; i < text.length(); i++) {
int len = matchWord(text, i);
if (len > 0) {
result.add(text.substring(i, i + len));
i += len - 1;
}
}
return result;
}
/**
* 敏感字符替换为*
*/
public String replaceSensitiveWord(String text) {
List<String> words = checkSensitiveWord(text);
for (String word : words) {
String replace = "*".repeat(word.length());
text = text.replace(word, replace);
}
return text;
}
private int matchWord(String text, int startIndex) {
boolean isEnd = false;
int matchLen = 0;
Map<String, Object> currentMap = dfaMap;
for (int i = startIndex; i < text.length(); i++) {
String c = String.valueOf(text.charAt(i));
currentMap = (Map<String, Object>) currentMap.get(c);
if (currentMap == null) {
break;
}
matchLen++;
if (Boolean.TRUE.equals(currentMap.get("isEnd"))) {
isEnd = true;
}
}
return isEnd ? matchLen : 0;
}
}
统一拦截实现:输入拦截器 + 输出统一响应处理
采用Spring组件全局拦截,和之前Sentinel流量防护、全局统一返回格式完美兼容。
@Component
public class SensitiveWordInterceptor implements HandlerInterceptor {
@Autowired
private SensitiveWordFilter sensitiveWordFilter;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 用户提问输入校验
String question = request.getParameter("question");
if (StringUtils.isNotBlank(question)) {
List<String> words = sensitiveWordFilter.checkSensitiveWord(question);
if (!words.isEmpty()) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(Result.fail("提问包含违规内容,请调整后重试")));
return false;
}
}
return true;
}
}
输出敏感词过滤我们采用 ResponseBodyAdvice 全局统一拦截,所有控制器返回结果在推送到前端之前自动执行敏感词替换,做到输入、输出双向防护。下面还有更重要的代码段、由于文章篇幅限制给出实现思路,完整源码放入配套资料包。
实现思路:
1、实现 ResponseBodyAdvice,重写 beforeBodyWrite;
2、只拦截 AI 问答相关返回结果,避免全局无差别处理损耗性能;
3、识别统一返回体 Result,提取回答内容执行敏感词替换;
4、组装新结果返回前端。
⚠️ 流式 SSE 长连接注意:SSE
SseEmitter不属于普通 ResponseBody,无法被该切面拦截。流式输出场景,需要在业务发送每一段文本时主动调用敏感词过滤器。
SSE是什么?怎么用?传说中的打字机,Chatjpt、豆包都在用
三、核心合规 2:三级细粒度权限管控
承接我们第11篇多租户隔离、第7篇RAG知识库内容,升级权限体系:角色权限 + 部门数据权限 + 工具调用权限,解决员工越权查看其他部门知识库文档痛点。
- 功能权限:普通员工/管理员/超级管理员,控制后台功能访问;
- 数据权限:检索知识库文档自动过滤所属部门,不同部门数据隔离(RAG核心需求);
- 工具权限:限制角色可调用工具,普通员工禁止访问财务、人事敏感接口。
登录用户上下文工具
/**
* 登录用户上下文工具
* 全局获取当前登录用户信息
*/
@Component
public class SecurityContextHolder {
private static final ThreadLocal<LoginUser> USER_THREAD_LOCAL = new ThreadLocal<>();
/**
* 设置当前登录用户
*/
public static void setCurrentUser(LoginUser loginUser) {
USER_THREAD_LOCAL.set(loginUser);
}
/**
* 获取当前登录用户
*/
public static LoginUser getCurrentUser() {
return USER_THREAD_LOCAL.get();
}
/**
* 清除上下文(防止内存泄漏,过滤器最后必须调用)
*/
public static void clear() {
USER_THREAD_LOCAL.remove();
}
}
AOP切面统一权限校验,侵入极低
@Aspect
@Component
public class PermissionAspect {
@Around("@annotation(requiresPermission)")
public Object checkPermission(ProceedingJoinPoint point, RequiresPermission requiresPermission) throws Throwable {
// 获取当前登录用户(和项目现有登录上下文保持一致)
LoginUser user = SecurityContextHolder.getCurrentUser();
// 校验角色功能权限
if (!user.getRoles().contains(requiresPermission.role())) {
throw new PermissionException("当前账号无此操作权限");
}
// 设置当前用户部门ID,后续Mybatis自动拼接数据权限SQL
DataPermissionHelper.setCurrentDeptId(user.getDeptId());
try {
return point.proceed();
} finally {
DataPermissionHelper.clear();
}
}
}
数据权限自动过滤,可以直接复用RAG文档检索Mapper,向量查询时追加dept_id筛选,实现多租户知识库隔离。
具体内容可查看:第11篇:多租户权限隔离!企业级知识库部门级权限管控方案(Java+AI落地实战系列 | 开箱即用 | 满足企业数据安全合规)
四、核心合规 3:全链路操作审计日志
等保硬性要求:所有用户交互行为可追溯。用户提问、文档上传、向量库新增文档、工具调用、管理员配置修改全部记录。
审计日志数据库实体
@Data
@TableName("ai_operation_log")
public class OperationLog {
private Long id;
private String userId; // 操作人ID
private String userName; // 操作人姓名
private String deptId; // 所属部门
private String operationType; // 操作类型:提问、上传文档、调用工具
private String content; // 操作内容
private String ip; // 操作IP地址
private LocalDateTime createTime; // 操作时间
private Integer success; // 操作是否成功 1成功 0失败
}
自定义注解代码
/**
* 操作审计日志注解
* 标记在Controller接口方法上,自动记录全链路操作日志
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作类型:提问、上传文档、调用工具、知识库管理等
*/
String type();
}
AOP自动记录审计日志,异步入库不阻塞主流程
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogMapper logMapper;
@Around("@annotation(operationLog)")
public Object recordLog(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
LoginUser user = SecurityContextHolder.getCurrentUser();
OperationLog log = new OperationLog();
log.setUserId(user.getUserId());
log.setUserName(user.getUserName());
log.setDeptId(user.getDeptId());
log.setOperationType(operationLog.type());
log.setContent(JSON.toJSONString(joinPoint.getArgs()));
log.setIp(ServletUtil.getClientIP());
log.setCreateTime(LocalDateTime.now());
// 默认标记成功
log.setSuccess(1);
Object result;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
// 发生异常,标记操作失败
log.setSuccess(0);
throw e;
} finally {
// 无论成功失败,异步写入日志
CompletableFuture.runAsync(() -> logMapper.insert(log));
}
return result;
}
}
五、新手落地必踩5个致命合规坑
坑1:只校验用户输入,忽略AI输出审核
很多开发只拦截用户提问,忽略大模型生成内容。AI极易被提示词诱导输出敏感信息,带来重大合规风险。
✅ 方案:输入、输出双向校验,AI回答返回前端之前强制经过敏感词过滤器。
坑2:权限仅前端控制,后端不做校验
前端隐藏按钮不等于权限控制,用户抓包直接调用接口,轻松实现越权访问其他部门知识库。
✅ 方案:权限校验强制放在后端AOP切面,前端仅做页面展示优化。
坑3:敏感词库杂乱,误杀率过高
直接网上下载通用敏感词库,正常业务词汇被拦截,严重影响员工使用体验。
✅ 方案:按企业业务自定义词库,分级管控:高危词汇直接拦截、普通违规词汇内容替换;持续迭代优化降低误判。
坑4:审计日志原样存储手机号、身份证等敏感信息
日志明文存储隐私数据,日志库一旦泄露,直接违反《个人信息保护法》。
✅ 方案:日志入库前统一脱敏,手机号、证件号中间字符使用*遮挡。
坑5:同步写入审计日志,拖慢问答接口
问答接口同步写入数据库,数据库压力大时直接造成接口超时。
✅ 方案:采用异步线程写入日志;高并发场景可引入消息队列削峰。
关注公众号 Java-AI 工程师(有好礼),

后台回复 系列源码 即可免费领取全部的源码和资料。每更新一篇,我都会往资料包里新增对应源码,跟着系列就能从零搭出完整的企业级 AI 系统。并且每周都有面试题刷哦
✅ 本篇学习完成
恭喜你,勇士,咱们Boss见,此时您已经能完整的手搓出来一个合规、高可用、高性能、高并发的AI系统了。
本系列共 21 篇,目前你已经完成了16篇的学习,21篇全部学习完成,包您涨薪。放松下,再回来看下面的优化建议和总结。劳逸结合才能把知识消化得更扎实。

浙公网安备 33010602011771号