档案管理系统合规资质申请与技术落地实操指南
一、数据库设计:构建“三员管理”权限架构
在申请档案管理系统相关资质(如涉密信息系统集成资质或等保三级)时,核心要求是必须具备系统管理员、安全保密员和安全审计员(简称“三员”)的权限分离。以下是基于MySQL 8.0的实操建表与权限配置脚本,可直接执行。
1. 创建用户基础表与角色枚举
执行以下SQL语句,建立严格的用户角色体系。注意角色字段使用了TINYINT类型,确保存储紧凑。
```sql CREATE DATABASE IF NOT EXISTS archive_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE archive_system; -- 创建用户表 CREATE TABLE `sys_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '用户名', `password` VARCHAR(255) NOT NULL COMMENT '密码(BCrypt加密)', `role_type` TINYINT(1) NOT NULL COMMENT '角色类型: 1-系统管理员, 2-安全保密员, 3-安全审计员', `status` TINYINT(1) DEFAULT 1 COMMENT '状态: 1-启用, 0-禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 初始化三员数据(密码为明文'123456'的BCrypt加密结果,实际使用请替换) INSERT INTO `sys_user` (`username`, `password`, `role_type`) VALUES ('sys_admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z2EHsM8lE9lBOsl7iKTVKIUi', 1), ('sec_officer', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z2EHsM8lE9lBOsl7iKTVKIUi', 2), ('auditor', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z2EHsM8lE9lBOsl7iKTVKIUi', 3); ```2. 配置权限隔离策略
在业务逻辑层,必须强制校验角色。以下是一个基于Spring Boot的拦截器配置代码片段,用于确保“三员”互不越权。
```java // RoleInterceptor.java public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String requestURI = request.getRequestURI(); Long currentUserId = (Long) request.getSession().getAttribute("userId"); Integer roleType = userService.getRoleTypeById(currentUserId); // 系统管理员(1)只能访问用户管理和系统配置 if (roleType == 1 && !requestURI.startsWith("/sys/config") && !requestURI.startsWith("/sys/user")) { throw new PermissionDeniedException("系统管理员无权访问此业务模块"); } // 安全保密员(2)负责策略分配,不能审计日志 if (roleType == 2 && requestURI.startsWith("/audit/log")) { throw new PermissionDeniedException("安全保密员无权查看审计日志"); } // 安全审计员(3)只能查看日志,不能修改业务数据 if (roleType == 3 && (requestURI.startsWith("/sys/config") || requestURI.startsWith("/archive/save"))) { throw new PermissionDeniedException("安全审计员无权修改业务数据"); } return true; } ```二、数据存储:国密SM4算法加密实战
档案管理系统涉及敏感信息,资质审核强制要求使用国产密码算法(SM4)。以下提供基于Java的SM4 ECB模式加密工具类,依赖Bouncy Castle库。
1. Maven依赖配置
在`pom.xml`中添加以下依赖,确保版本兼容性。
```xml2. SM4加密工具类实现
此类提供了完整的加密和解密功能,密钥长度必须为16字节。请将此类直接复制到项目中使用。
```java import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.security.Security; public class SM4Util { static { Security.addProvider(new BouncyCastleProvider()); } private static final String ALGORITHM_NAME = "SM4"; private static final String KEY_CHARSET = "UTF-8"; private static final int KEY_SIZE = 128; // 生成随机密钥 public static String generateKey() throws Exception { KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME); kg.init(KEY_SIZE, new SecureRandom()); SecretKey secretKey = kg.generateKey(); return new String(secretKey.getEncoded(), KEY_CHARSET).substring(0, 16); } // 加密 public static String encryptEcb(String key, String data) throws Exception { if (key == null || key.length() != 16) throw new IllegalArgumentException("SM4密钥长度必须为16位"); Cipher cipher = Cipher.getInstance(ALGORITHM_NAME + "/ECB/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec sm4Key = new SecretKeySpec(key.getBytes(KEY_CHARSET), ALGORITHM_NAME); cipher.init(Cipher.ENCRYPT_MODE, sm4Key); byte[] encrypted = cipher.doFinal(data.getBytes(KEY_CHARSET)); return bytesToHex(encrypted); } // 解密 public static String decryptEcb(String key, String cipherText) throws Exception { if (key == null || key.length() != 16) throw new IllegalArgumentException("SM4密钥长度必须为16位"); Cipher cipher = Cipher.getInstance(ALGORITHM_NAME + "/ECB/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME); SecretKeySpec sm4Key = new SecretKeySpec(key.getBytes(KEY_CHARSET), ALGORITHM_NAME); cipher.init(Cipher.DECRYPT_MODE, sm4Key); byte[] decrypted = cipher.doFinal(hexToBytes(cipherText)); return new String(decrypted, KEY_CHARSET); } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } private static byte[] hexToBytes(String hexString) { int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return data; } } ```三、系统审计:AOP切面实现全量操作记录
资质审查要求系统必须留存至少6个月的详细审计日志,且日志内容不可被篡写。使用Spring AOP切面技术可以无侵入地记录所有关键操作。
1. 自定义审计注解

创建`@LogAudit`注解,用于标记需要记录的方法。
```java import java.lang.annotation.; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAudit { String operation() default ""; // 操作描述,如"删除档案" String module() default ""; // 模块名称 } ```2. AOP切面实现
以下代码实现了日志的自动采集,包括用户IP、操作参数、执行结果和耗时。
```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; @Aspect @Component public class AuditLogAspect { @Around("@annotation(com.yourpackage.annotation.LogAudit)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = null; Throwable exception = null; try { result = joinPoint.proceed(); return result; } catch (Throwable e) { exception = e; throw e; } finally { long duration = System.currentTimeMillis() - startTime; saveLog(joinPoint, result, exception, duration); } } private void saveLog(ProceedingJoinPoint joinPoint, Object result, Throwable exception, long duration) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); LogAudit logAnnotation = method.getAnnotation(LogAudit.class); // 获取请求上下文(需注入HttpServletRequest) // String ip = request.getRemoteAddr(); // String username = SecurityContextHolder.getContext().getAuthentication().getName(); // 构造日志对象 AuditLog log = new AuditLog(); log.setModule(logAnnotation.module()); log.setOperation(logAnnotation.operation()); log.setMethod(signature.getDeclaringTypeName() + "." + signature.getName()); log.setParams(joinPoint.getArgs().toString()); // 实际生产建议JSON化并脱敏 log.setStatus(exception == null ? "SUCCESS" : "FAIL"); log.setErrorMsg(exception != null ? exception.getMessage() : ""); log.setDuration(duration); log.setCreateTime(new Date()); // 异步保存到数据库或独立的日志文件 auditLogService.insert(log); } } ```四、访问控制:Nginx强制HTTPS与安全Header
资质验收中,网络传输必须加密。以下是Nginx配置,强制跳转HTTPS并添加防点击劫持等安全响应头。
1. Nginx配置文件修改
编辑`/etc/nginx/nginx.conf`或对应的站点配置文件`/etc/nginx/conf.d/archive.conf`。
```nginx server { listen 80; server_name archive.yourdomain.com; 强制将所有HTTP请求重定向至HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl; server_name archive.yourdomain.com; SSL证书配置(请替换为实际证书路径) ssl_certificate /etc/nginx/ssl/yourdomain.pem; ssl_certificate_key /etc/nginx/ssl/yourdomain.key; 优化SSL安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; 安全响应头配置(满足等保要求) add_header X-Frame-Options "SAMEORIGIN"; 防止点击劫持 add_header X-Content-Type-Options "nosniff"; 防止MIME类型嗅探 add_header X-XSS-Protection "1; mode=block"; XSS保护 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; HSTS location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ```2. 配置生效命令
修改配置后,执行以下命令检测语法并重载服务。
```bash 检测Nginx配置文件语法 nginx -t 若显示syntax is ok,则重载配置 systemctl reload nginx ```五、防泄漏:前端动态水印实现
为了满足防止截屏泄密的要求,前端页面必须包含包含当前用户信息的动态水印。以下是基于原生JavaScript和Canvas的实现方案。
1. 水印生成代码
将以下代码保存为`watermark.js`,并在系统主页面引入。
```javascript function initWatermark(options) { const settings = Object.assign({ text: '机密文件', container: document.body, width: 300, height: 200, rotate: -20, fontSize: 16, color: 'rgba(200, 200, 200, 0.2)' }, options); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = settings.width; canvas.height = settings.height; ctx.rotate(settings.rotate Math.PI / 180); ctx.font = `${settings.fontSize}px Arial`; ctx.fillStyle = settings.color; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(settings.text, 0, settings.height / 2); const base64Url = canvas.toDataURL(); const watermarkDiv = document.createElement('div'); watermarkDiv.style.position = 'fixed'; watermarkDiv.style.left = '0'; watermarkDiv.style.top = '0'; watermarkDiv.style.width = '100%'; watermarkDiv.style.height = '100%'; watermarkDiv.style.zIndex = '9999'; watermarkDiv.style.pointerEvents = 'none'; // 不阻挡鼠标事件 watermarkDiv.style.background = `url(${base64Url}) repeat`; settings.container.appendChild(watermarkDiv); } // 调用示例:页面加载完成后执行 window.onload = function() { // 假设从后端获取当前用户名 const currentUser = "当前用户:张三"; initWatermark({ text: `${currentUser} | 严禁外传 | ${new Date().toLocaleDateString()}`, rotate: -30 }); }; ```2. 集成到HTML
在`index.html`的`
`标签末尾添加脚本引用。 ```html ```