档案管理软件身份认证优化实操指南

一、核心问题与优化目标

档案管理软件的身份认证模块常面临以下问题:

  • 单一密码认证,安全性不足
  • 用户权限管理复杂,易出错
  • 登录体验差,影响工作效率
  • 缺乏登录审计,不合规风险高

本次优化目标:构建一个支持多因素认证、细粒度权限控制、操作便捷且具备完整审计日志的身份认证系统。

二、技术选型与架构设计

采用基于JWT(JSON Web Token)的无状态认证,结合RBAC(基于角色的访问控制)模型。核心组件:

  • 认证服务器:负责用户登录、令牌签发与验证
  • 权限中心:管理用户、角色、权限的映射关系
  • 客户端拦截器:在业务请求前验证令牌与权限

整体流程:用户登录 -> 认证服务器验证凭证并签发JWT -> 客户端携带JWT访问业务接口 -> 网关/拦截器验证JWT有效性及权限 -> 访问通过或拒绝。

三、环境准备与依赖配置

3.1 后端环境(以Spring Boot为例)

在项目的pom.xml文件中添加以下依赖:

``` io.jsonwebtoken jjwt-api 0.11.5 io.jsonwebtoken jjwt-impl 0.11.5 runtime io.jsonwebtoken jjwt-jackson 0.11.5 runtime org.springframework.boot spring-boot-starter-data-redis ```

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) { Map claims = new HashMap<>(); claims.put("userId", userId); claims.put("role", role); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SECRET_KEY, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody(); } public static boolean validateToken(String token) { try { parseToken(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } } ```

4.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. 查询用户角色(示例取第一个角色) List roleCodes = userService.getRoleCodesByUserId(user.getId()); if (roleCodes.isEmpty()) { return Result.error("用户未分配角色"); } // 3. 生成JWT令牌 String token = JwtUtil.generateToken(user.getUsername(), user.getId(), roleCodes.get(0)); // 4. 更新最后登录时间并记录成功日志 userService.updateLastLoginTime(user.getId()); loginLogService.saveLog(user, httpRequest, 1); // 5. 返回令牌和用户基本信息 Map data = new HashMap<>(); data.put("token", token); data.put("userInfo", new UserInfoVO(user.getId(), user.getUsername(), user.getRealName(), roleCodes)); return Result.success(data); } } ```

4.3 权限拦截器实现

创建AuthInterceptor.java:

``` @Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头获取token String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401,\"message\":\"未提供认证令牌\"}"); return false; } String token = authHeader.substring(7); // 2. 验证token有效性 if (!JwtUtil.validateToken(token)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401,\"message\":\"令牌无效或已过期\"}"); return false; } // 3. 解析token获取用户信息 Claims claims = JwtUtil.parseToken(token); Long userId = claims.get("userId", Long.class); String role = claims.get("role", String.class); // 4. 检查令牌是否在黑名单(用于实现登出功能) String blacklistKey = "token:blacklist:" + token; if (Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey))) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401,\"message\":\"令牌已失效\"}"); return false; } // 5. 将用户信息存入请求上下文,供后续使用 request.setAttribute("userId", userId); request.setAttribute("role", role); // 6. 权限校验(示例:根据请求路径和角色判断) String requestURI = request.getRequestURI(); String requestMethod = request.getMethod(); if (!hasPermission(role, requestURI, requestMethod)) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write("{\"code\":403,\"message\":\"无权访问该资源\"}"); return false; } return true; } private boolean hasPermission(String roleCode, String uri, String method) { // 此处应查询数据库或缓存,判断该角色是否有此URI和方法的访问权限 // 简化示例:从缓存获取角色权限列表进行匹配 String permKey = "role:perms:" + roleCode; List permList = (List) redisTemplate.opsForValue().get(permKey); if (permList == null) { // 缓存未命中,从数据库查询并缓存 permList = permissionService.getPermCodesByRoleCode(roleCode); redisTemplate.opsForValue().set(permKey, permList, 1, TimeUnit.HOURS); } // 将URI和方法转换为权限标识进行匹配,如:GET:/api/users -> user:list String requiredPerm = convertToPermCode(uri, method); return permList.contains(requiredPerm); } } ```

注册拦截器到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
AI咨询
热线电话

028-85154420

15388110056

全国售前咨询电话

扫码咨询
安答联动微信公众号二维码

微信扫码关注安答联动

申请试用
热线电话
申请试用

安答联动档案管理系统