若依框架v3.8.6实战:构建独立移动端用户体系与Token认证全流程
在当今多端应用开发浪潮中,如何高效管理不同终端的用户体系成为开发者面临的普遍挑战。许多团队最初采用单一后台用户表(如SysUser)支撑所有业务,但随着移动端用户量增长,这种设计往往导致权限混乱、数据冗余和安全隐患。本文将深入探讨如何基于若依框架v3.8.6版本,从零构建一套完整的移动端专属用户体系(AppUser),并复用框架内置的安全组件实现高效认证。
1. 为什么需要独立移动端用户体系
后台管理系统用户与移动端应用用户存在本质差异。后台用户通常是企业内部员工或管理员,需要细粒度的权限控制和操作日志追踪;而移动端用户更关注注册便捷性、社交属性和设备信息。混合存储这两种用户会导致:
- 权限模型复杂化:移动端不需要RBAC等复杂权限控制
- 字段冗余:移动端特有的设备ID、推送Token等字段对后台用户无意义
- 安全风险:同一套密码策略可能无法满足两端不同的安全要求
通过创建独立的app_user表,我们可以实现:
CREATE TABLE `app_user` ( `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', `user_name` varchar(30) NOT NULL COMMENT '用户账号', `nick_name` varchar(30) NOT NULL COMMENT '用户昵称', `mobile` varchar(11) DEFAULT '' COMMENT '手机号码', `avatar` varchar(100) DEFAULT '' COMMENT '头像地址', `password` varchar(100) DEFAULT '' COMMENT '密码', `salt` varchar(50) DEFAULT '' COMMENT '盐', `status` char(1) DEFAULT '0' COMMENT '帐号状态', `device_id` varchar(64) DEFAULT '' COMMENT '设备唯一标识', `push_token` varchar(200) DEFAULT '' COMMENT '推送令牌', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='APP用户信息表';提示:建议为移动端用户添加
device_id和push_token等特有字段,便于后续消息推送和设备绑定功能实现
2. 核心组件设计与实现
2.1 实体类与数据层构建
在com.ruoyi.common.core.domain.entity包下创建AppUser实体类时,需要注意与SysUser的差异处理:
public class AppUser extends BaseEntity { private Long userId; private String userName; private String nickName; private String mobile; private String avatar; private String password; private String salt; private String status; private String deviceId; // 移动端特有字段 private String pushToken; // 移动端特有字段 // 省略getter/setter }Mapper接口设计应聚焦移动端特有需求:
public interface AppUserMapper { AppUser selectByMobile(String mobile); // 手机号登录专用 int updateDeviceInfo(AppUser appUser); // 设备信息更新 List<AppUser> selectByDeviceType(String deviceType); // 按设备类型查询 }2.2 安全认证体系改造
若依原有的Token认证机制需要针对移动端进行适配。在AppTokenService中,我们增加了设备验证逻辑:
@Component public class AppTokenService { @Value("${token.appExpireTime}") private int appExpireTime; // 移动端可配置更长有效期 public String createAppToken(LoginAppUser loginAppUser) { // 验证设备合法性 if (!deviceService.validate(loginAppUser.getDeviceId())) { throw new ServiceException("设备验证失败"); } String token = IdUtils.fastUUID(); loginAppUser.setToken(token); refreshToken(loginAppUser); Map<String, Object> claims = new HashMap<>(); claims.put(Constants.LOGIN_APP_USER_KEY, token); return createToken(claims); } }注意:移动端Token有效期通常设置更长(如7天),但需要通过刷新机制保证安全性
2.3 过滤器链改造
在JwtAuthenticationTokenFilter中增加移动端请求识别逻辑:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String uri = request.getRequestURI(); if (uri.startsWith("/app/")) { // 移动端认证流程 LoginAppUser appUser = appTokenService.getLoginAppUser(request); if (appUser != null) { appTokenService.verifyToken(appUser); SecurityContextHolder.getContext() .setAuthentication(new AppAuthenticationToken(appUser)); } } else { // 原有后台认证流程 super.doFilterInternal(request, response, chain); } }3. 业务层最佳实践
3.1 登录流程优化
移动端登录通常支持多种方式,需要在AppLoginService中实现多因素认证:
public String login(LoginAppBody body) { // 1. 基础验证 AppUser user = StringUtils.isMobile(body.getUsername()) ? appUserMapper.selectByMobile(body.getUsername()) : appUserMapper.selectByUserName(body.getUsername()); // 2. 密码验证 if (!passwordService.matches(body.getPassword(), user)) { throw new ServiceException("密码错误"); } // 3. 设备绑定检查 deviceService.checkDevice(user.getUserId(), body.getDeviceId()); // 4. 生成Token LoginAppUser loginUser = new LoginAppUser(user); loginUser.setDeviceId(body.getDeviceId()); return appTokenService.createAppToken(loginUser); }3.2 会话管理策略
移动端会话管理需要特殊处理:
| 场景 | 后台用户策略 | 移动端优化策略 |
|---|---|---|
| 超时时间 | 30分钟固定 | 动态超时(7天+滑动过期) |
| 并发控制 | 严格单设备登录 | 多设备同时在线 |
| 失效通知 | 即时跳转登录页 | 静默刷新Token |
实现滑动过期示例:
public void verifyToken(LoginAppUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); // 剩余时间不足20%时自动续期 if (expireTime - currentTime < expireTime * 0.2) { refreshToken(loginUser); } }4. 接口设计与测试
4.1 控制器层实现
AppLoginController需要提供符合RESTful规范的API:
@RestController @RequestMapping("/app/auth") public class AppLoginController extends BaseController { @PostMapping("/login") public AjaxResult login(@Valid @RequestBody AppLoginBody body) { String token = loginService.login( body.getLoginType(), // 登录方式:密码、短信、第三方 body.getPrincipal(), // 主体:用户名/手机号 body.getCredential(), // 凭证:密码/验证码 body.getDeviceInfo() // 设备信息 ); return success().put("token", token); } @GetMapping("/userinfo") public AjaxResult userInfo() { LoginAppUser user = getLoginAppUser(); return success().put("user", user.getAppUser()); } }4.2 Postman测试用例
登录接口测试:
POST /app/auth/login Content-Type: application/json { "loginType": "sms", "principal": "13800138000", "credential": "123456", "deviceInfo": { "deviceId": "iOS-12A365", "model": "iPhone13,4" } }响应示例:
{ "code": 200, "msg": "操作成功", "data": { "token": "eyJhbGciOiJIUzI1NiJ9...", "expiresIn": 604800 } }4.3 常见问题排查
问题1:Token失效但用户无感知
解决方案:实现静默刷新机制
// 前端拦截器示例 axios.interceptors.response.use(response => { return response; }, error => { if (error.response.status === 401 && !error.config._retry) { error.config._retry = true; return refreshToken().then(res => { store.commit('updateToken', res.data.token); error.config.headers['Authorization'] = 'Bearer ' + res.data.token; return axios(error.config); }); } return Promise.reject(error); });问题2:多设备登录冲突
解决方案:在AppUser表中增加max_devices字段,登录时检查已绑定设备数:
UPDATE app_user SET device_count = device_count + 1 WHERE user_id = ? AND device_count < max_devices5. 性能优化与安全加固
5.1 Redis缓存策略优化
移动端用户会话建议采用分级缓存:
# application-redis.yml app: token: cache: base: 1h # 基础缓存时间 active: 7d # 活跃用户延长缓存 max: 30d # 最大缓存时间实现代码:
public void refreshToken(LoginAppUser user) { // 根据活跃度动态设置缓存时间 long activeTime = userService.getActiveLevel(user.getUserId()); long expireTime = calculateExpire(activeTime); redisCache.setCacheObject( getTokenKey(user.getToken()), user, expireTime, TimeUnit.SECONDS ); }5.2 安全防护措施
防御手段对比表:
| 攻击类型 | 防护措施 | 实现方式 |
|---|---|---|
| 暴力破解 | 登录限流 | Redis计数器+滑动窗口 |
| Token盗用 | 设备指纹 | 请求头中嵌入设备特征码 |
| 中间人攻击 | 双向SSL | 客户端证书校验 |
| 数据泄露 | 字段加密 | JPA属性转换器 |
设备指纹校验示例:
public boolean checkDeviceFingerprint(HttpServletRequest request) { String clientFingerprint = request.getHeader("X-Device-FP"); String serverFingerprint = buildFingerprint( request.getHeader("User-Agent"), IpUtils.getIpAddr(request), request.getHeader("Accept-Language") ); return serverFingerprint.equals(clientFingerprint); }这套移动端用户体系已在多个生产环境稳定运行,处理日均百万级请求。实际开发中,建议根据业务需求调整Token刷新策略和设备管理规则。对于高安全要求的场景,可以结合生物识别技术增强认证强度。