档案管理软件身份认证优化实操指南
一、核心问题与优化目标
档案管理软件的身份认证模块常面临以下问题:
- 单一密码认证,安全性不足
- 用户权限管理复杂,易出错
- 登录体验差,影响工作效率
- 缺乏登录审计,不合规风险高
本次优化目标:构建一个支持多因素认证、细粒度权限控制、操作便捷且具备完整审计日志的身份认证系统。
二、技术选型与架构设计
采用基于JWT(JSON Web Token)的无状态认证,结合RBAC(基于角色的访问控制)模型。核心组件:
- 认证服务器:负责用户登录、令牌签发与验证
- 权限中心:管理用户、角色、权限的映射关系
- 客户端拦截器:在业务请求前验证令牌与权限
整体流程:用户登录 -> 认证服务器验证凭证并签发JWT -> 客户端携带JWT访问业务接口 -> 网关/拦截器验证JWT有效性及权限 -> 访问通过或拒绝。
三、环境准备与依赖配置
3.1 后端环境(以Spring Boot为例)
在项目的pom.xml文件中添加以下依赖:
```3.2 数据库表结构
创建以下核心表:
``` -- 用户表 CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用', `last_login_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uni_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 角色表 CREATE TABLE `sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `role_code` varchar(50) NOT NULL COMMENT '角色编码', `role_name` varchar(50) NOT NULL COMMENT '角色名称', PRIMARY KEY (`id`), UNIQUE KEY `uni_role_code` (`role_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 权限表(对应前端菜单或API接口) CREATE TABLE `sys_permission` ( `id` bigint NOT NULL AUTO_INCREMENT, `perm_code` varchar(100) NOT NULL COMMENT '权限标识,如:user:add', `perm_name` varchar(50) NOT NULL COMMENT '权限名称', `resource_type` varchar(20) DEFAULT 'API' COMMENT '资源类型:MENU, API, BUTTON', `resource_path` varchar(255) DEFAULT NULL COMMENT '资源路径,如API接口URL', PRIMARY KEY (`id`), UNIQUE KEY `uni_perm_code` (`perm_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用户角色关联表 CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL, `role_id` bigint NOT NULL, PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 角色权限关联表 CREATE TABLE `sys_role_permission` ( `role_id` bigint NOT NULL, `perm_id` bigint NOT NULL, PRIMARY KEY (`role_id`,`perm_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 登录审计日志表 CREATE TABLE `sys_login_log` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `login_ip` varchar(50) DEFAULT NULL, `login_time` datetime DEFAULT CURRENT_TIMESTAMP, `status` tinyint DEFAULT NULL COMMENT '0-失败,1-成功', `user_agent` text COMMENT '浏览器信息', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ```四、核心功能实现步骤
4.1 JWT工具类封装
创建JwtUtil.java类:
``` import io.jsonwebtoken.; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { // 从配置文件中读取,此处示例 private static final String SECRET_KEY_STRING = "YourSecretKeyShouldBeLongAndComplexEnoughToBeSecure"; private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(SECRET_KEY_STRING.getBytes()); private static final long EXPIRATION_TIME = 86400000; // 24小时,单位毫秒 public static String generateToken(String username, Long userId, String role) { Map4.2 用户登录与令牌签发
创建AuthController.java:
``` @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @Autowired private LoginLogService loginLogService; @PostMapping("/login") public Result login(@RequestBody LoginRequest request, HttpServletRequest httpRequest) { // 1. 验证用户名密码 SysUser user = userService.findByUsername(request.getUsername()); if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { loginLogService.saveLog(user, httpRequest, 0); return Result.error("用户名或密码错误"); } if (user.getStatus() == 0) { loginLogService.saveLog(user, httpRequest, 0); return Result.error("账号已被禁用"); } // 2. 查询用户角色(示例取第一个角色) List4.3 权限拦截器实现
创建AuthInterceptor.java:
``` @Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate注册拦截器到Spring配置:
``` @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/") // 拦截所有API请求 .excludePathPatterns("/api/auth/login", "/api/auth/logout"); // 排除登录登出 } } ```4.4 用户登出与令牌失效

在AuthController中添加登出接口:
``` @PostMapping("/logout") public Result logout(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); // 将令牌加入Redis黑名单,设置过期时间与令牌剩余有效期一致 long expiration = JwtUtil.parseToken(token).getExpiration().getTime(); long currentTime = System.currentTimeMillis(); long ttl = expiration - currentTime; if (ttl > 0) { String blacklistKey = "token:blacklist:" + token; redisTemplate.opsForValue().set(blacklistKey, "1", ttl, TimeUnit.MILLISECONDS); } } return Result.success("登出成功"); } ```五、增强安全与多因素认证
5.1 密码安全策略
使用BCryptPasswordEncoder进行密码加密:
``` @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } ```用户注册或修改密码时:
``` String encodedPassword = passwordEncoder.encode(rawPassword); user.setPassword(encodedPassword); ```5.2 短信/邮箱验证码集成
在登录接口前增加验证码验证步骤:
``` // 1. 验证验证码(以短信为例) String cacheKey = "login:sms:" + request.getPhone(); String correctCode = (String) redisTemplate.opsForValue().get(cacheKey); if (correctCode == null || !correctCode.equals(request.getSmsCode())) { return Result.error("验证码错误或已过期"); } // 验证通过后删除缓存中的验证码 redisTemplate.delete(cacheKey); // 2. 继续原有的用户名密码验证流程 ```5.3 登录失败锁定机制
在登录验证逻辑中添加:
``` String failKey = "login:fail:" + request.getUsername(); Integer failCount = (Integer) redisTemplate.opsForValue().get(failKey); if (failCount != null && failCount >= 5) { // 检查锁定时间 String lockKey = "login:lock:" + request.getUsername(); if (Boolean.TRUE.equals(redisTemplate.hasKey(lockKey))) { return Result.error("账号已锁定,请30分钟后再试"); } } // 密码验证失败时 if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { if (failCount == null) { redisTemplate.opsForValue().set(failKey, 1, 1, TimeUnit.HOURS); } else { redisTemplate.opsForValue().increment(failKey); if (failCount + 1 >= 5) { // 锁定30分钟 redisTemplate.opsForValue().set("login:lock:" + request.getUsername(), "1", 30, TimeUnit.MINUTES); } } // ... 记录日志并返回错误 } ```六、前端集成示例
6.1 登录请求
使用axios发送登录请求:
``` import axios from 'axios'; const login = async (username, password) => { try { const response = await axios.post('/api/auth/login', { username, password }); if (response.data.code === 200) { const { token, userInfo } = response.data.data; // 存储token到localStorage localStorage.setItem('token', token); localStorage.setItem('userInfo', JSON.stringify(userInfo)); // 设置axios默认请求头 axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; return { success: true, data: userInfo }; } else { return { success: false, message: response.data.message }; } } catch (error) { return { success: false, message: '网络请求失败' }; } }; ```6.2 请求拦截器
配置axios请求拦截器自动添加token:
``` axios.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } ); ```6.3 响应拦截器处理token过期
配置axios响应拦截器:
``` axios.interceptors.response.use( response => { return response; }, error => { if (error.response && error.response.status === 401) { // token过期或无效,清除本地存储并跳转到登录页 localStorage.removeItem('token'); localStorage.removeItem('userInfo'); window.location.href = '/login'; } return Promise.reject(error); } ); ```七、部署与测试
7.1 配置文件
application.yml关键配置:
``` server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/archive_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password: your_redis_password database: 0 timeout: 3000ms jwt: secret: YourSecretKeyShouldBeLongAndComplexEnoughToBeSecure expiration: 86400000 ```7.2 初始化数据脚本
创建初始化角色和权限的SQL:
``` -- 插入管理员角色 INSERT INTO sys