前言
在中后台系统开发中,用户登录是整个系统的入口。一个安全、灵活、可扩展的登录模块,不仅能保障系统的安全性,还能为后续功能(如权限控制、日志审计、多端登录)提供坚实的基础。
本文将以开源项目 mldong 中的登录模块为例,深入解析其设计思路与实现细节,展示它在实际项目中的灵活性与实用性。
✅ 项目地址 :gitee.com/mldong/mldo...
登录模块的核心设计亮点
✅ 1. 多种授权方式支持(Grant Type)
mldong 支持多种登录方式,包括但不限于:
- 账号密码登录
- 短信验证码登录
- 微信授权码登录
- 微信手机号授权登录
通过 ILoginGranter 接口 + Spring IOC 自动注入的方式,实现了高度解耦和灵活扩展。
优势:业务方可以根据需求快速新增或替换登录方式,而无需修改核心逻辑。
✅ 2. 登录前后处理器插件化(LoginHandler)
提供了 LoginHandler 插件机制,允许在登录前后插入自定义逻辑,例如:
- 更新最近登录时间;
- 发送登录通知;
- 绑定设备指纹信息;
- 同步第三方数据。
优势:增强系统的可扩展性,避免业务代码侵入登录主流程。
✅ 3. Sa-Token 集成实现统一鉴权管理
使用 Sa-Token 实现统一的 Token 管理,具备以下能力:
- 多端登录支持;
- Token 自动续期;
- Token 强制退出;
- 用户切换(扮演/退出扮演);
- 登录状态保持。
优势:简化了会话管理,提升了系统的安全性和易用性。
✅ 4. 安全机制完善
- 使用加盐 MD5 加密存储用户密码;
- 可配置是否开启图形验证码;
- 登录失败记录日志,便于风控分析;
- 超级管理员账号保护机制。
优势:防止暴力破解、提升系统安全性。
✅ 5. 登录用户信息丰富、结构清晰
封装完整的 LoginUser 对象,包含:
- 基础信息(ID、姓名、手机号等);
- 角色信息(角色 ID、名称、编码);
- 所属机构(部门名称、编码);
- 当前应用编码;
- 是否超级管理员标识;
- 登录上下文(IP、浏览器、登录时间);
- 扩展字段(用于扮演用户、第三方登录等场景)。
优势:前端可根据这些信息做菜单渲染、权限判断、个性化展示等操作。
✅ 6. 日志记录全面
所有登录行为均被记录至数据库,包括:
- 登录人、IP、时间、结果;
- 成功或失败原因;
- 操作详情。
优势:方便后续进行审计、异常排查、风控分析。
登录模块的设计与实现详解
✅ 1. 接口定义 ------ AuthController
java
@PostMapping("/sys/login")
public CommonResult<?> login(@RequestBody @Validated LoginParam param)
- 使用
@Validated
注解校验请求参数; - 结合 CaptchaManager 控制是否启用验证码;
- 最终调用 AuthService.login(param) 进行登录处理。
✅ 2. 登录服务实现 ------ AuthServiceImpl
方法签名:
java
@Override
public LoginToken login(LoginParam param)
主要流程如下:
- 获取当前请求的 appCode 和 grantType
- 根据用户名/手机号查找用户
- 密码验证(MD5 + Salt)
- 或者通过 ILoginGranter 动态获取用户信息
- 执行登录前处理器([preLogin](gitee.com/mldong/mldo...
- 构建 LoginUser 并执行登录后处理器(postLogin)
- 使用 Sa-Token 登录并生成 Token
- 记录登录日志
- 返回 LoginToken
✅ 3. 登录用户对象构建 ------ toLoginUser(User user)
该方法负责将数据库中的 User 对象转换为包含完整权限信息的 LoginUser,并补充:
- 登录 IP;
- 浏览器信息;
- 角色列表;
- 所属部门与岗位;
- 当前应用编码;
- 是否为超级管理员;
- 扩展字段(如扮演用户信息)。
✅ 4. 第三方授权登录支持
通过 ILoginGranter 接口 + Spring 的 Map<String, ILoginGranter>
注入机制,动态选择登录授权者:
java
ILoginGranter granter = loginGranterMap.get(param.getGrantType()+"Granter");
Dict paramDict = granter.grant(QueryParamHolder.me());
user = BeanUtil.toBean(paramDict, User.class);
示例授权类型:
passwordGranter
:账号密码登录-默认实现;w<font style="color:#080808;background-color:#ffffff;">xMa</font>Granter
:微信授权码登录-框架已实现;w<font style="color:#080808;background-color:#ffffff;">xMaPhone</font>Granter
:微信手机号授权-框架已实现;smsGranter
:短信验证码登录-框架未实现;
✅ 5. 登录前后处理器插件机制
通过 Map<String, LoginHandler>
注入多个实现类,实现登录前后处理:
java
loginHandlerMap.forEach((key, loginHandler)->{
loginHandler.preLogin(userDict); // 登录前处理
});
loginHandlerMap.forEach((key, loginHandler)->{
loginHandler.postLogin(loginUser); // 登录后处理
});
典型应用场景:
- 插入风控规则;
- 发送登录通知;
- 同步第三方数据。
✅ 6. Sa-Token 登录管理
使用 Sa-Token 实现统一的登录状态管理:
java
SaLoginParameter loginModel = new SaLoginParameter();
final LoginUser loginUser = toLoginUser(user);
loginModel.setExtra(CommonConstant.LOGIN_USER_KEY, loginUser);
StpUtil.login(user.getId(), loginModel);
优势:
- 支持多端登录;
- 可设置 Token 过期时间;
- 支持 Token 强制退出、切换账户等功能。
✅ 7. 登录日志记录
所有登录行为均被记录至数据库:
java
visLogService.saveVisLog(VisTypeEnum.LOGIN, param.getUserName(), "Y", "登录成功");
建议:后续可接入 ELK、Prometheus 实现登录监控与异常检测。
新增登录方式示例:短信验证码登录(SmsGranter)
为了进一步说明 mldong 框架在扩展登录方式上的灵活性和实用性,我们以 短信验证码登录 为例,展示如何快速实现一个新的登录授权器。
✅ 1. 接口实现类 ------ SmsGranter
java
package com.mldong.modules.sys.auth.granter;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.log.Log;
import com.mldong.auth.ILoginGranter;
import com.mldong.auth.err.AuthErrEnum;
import com.mldong.consts.CommonConstant;
import com.mldong.exception.ServiceException;
import com.mldong.modules.sys.entity.User;
import com.mldong.modules.sys.enums.AdminTypeEnum;
import com.mldong.modules.sys.service.AuthService;
import com.mldong.modules.sys.service.UserService;
import com.mldong.util.SmsUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 短信验证码登录授权器
* @author mldong
* @date 2024/10/16
*/
@Component
@RequiredArgsConstructor
public class SmsGranter implements ILoginGranter {
private static final Log log = Log.get();
private final UserService userService;
@Override
public Dict grant(Dict param) {
String phone = param.getStr("mobilePhone");
String code = param.getStr("code");
if (StrUtil.isBlank(phone) || StrUtil.isBlank(code)) {
ServiceException.throwBiz(AuthErrEnum.MISSING_SMS_PARAM);
}
// 验证短信验证码是否正确
boolean isValid = SmsUtil.validateCode(phone, code);
if (!isValid) {
log.warn("短信验证码错误,手机号:{}", phone);
ServiceException.throwBiz(AuthErrEnum.INVALID_SMS_CODE);
}
// 查询用户是否存在
User user = userService.getByPhone(phone);
// 用户不存在则自动注册(可选)
if (user == null) {
user = userService.createUserByPhone(phone, AdminTypeEnum.COMMON_ADMIN.getCode(), "短信验证码自动注册");
if (user == null) {
ServiceException.throwBiz(AuthErrEnum.USER_CREATE_FAIL);
}
}
return BeanUtil.toBean(user, Dict.class);
}
}
✅ 2. 工具类支持 ------ SmsUtil
java
package com.mldong.util;
import cn.hutool.core.map.MapUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 短信验证码工具类
* @author mldong
* @date 2024/10/16
*/
@Component
public class SmsUtil {
private static final String SMS_CODE_PREFIX = "sms:login:";
private final StringRedisTemplate redisTemplate;
public SmsUtil(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 校验短信验证码
* @param phone 手机号
* @param inputCode 输入的验证码
* @return 是否有效
*/
public boolean validateCode(String phone, String inputCode) {
String cacheKey = SMS_CODE_PREFIX + phone;
String storedCode = redisTemplate.opsForValue().get(cacheKey);
return inputCode.equals(storedCode);
}
/**
* 存储验证码(供发送服务调用)
* @param phone 手机号
* @param code 验证码
*/
public void saveCode(String phone, String code) {
String cacheKey = SMS_CODE_PREFIX + phone;
redisTemplate.opsForValue().set(cacheKey, code, 5, TimeUnit.MINUTES);
}
}
✅ 3. 异常枚举补充
在你的 AuthErrEnum.java
中添加如下枚举值:
java
MISSING_SMS_PARAM(10041005, "缺少短信登录参数"),
INVALID_SMS_CODE(10041006, "短信验证码无效或已过期"),
USER_CREATE_FAIL(10041007, "用户创建失败");
✅ 4. 请求参数格式
前端调用 /sys/login
接口时,传入如下参数即可触发短信验证码登录逻辑:
json
{
"grantType": "sms",
"mobilePhone": "13800001111",
"code": "123456"
}
✅ 5. 实现说明
步骤 | 说明 |
---|---|
参数验证 | 判断手机号和验证码是否为空 |
验证码校验 | 使用 Redis 缓存验证码并进行比对 |
自动注册 | 如果用户不存在,自动创建用户(可配置) |
返回用户信息 | 最终返回 Dict 用户信息,供后续流程使用 |
✅ 6. 优势与意义
- ✅ 轻量级:无需复杂依赖,仅需 Redis 和短信服务;
- ✅ 高复用性:结构清晰,可直接用于注册、找回密码等场景;
- ✅ 易接入 :只需实现 ILoginGranter 接口,Spring 自动识别;
- ✅ 安全性强:验证码有效期控制、防暴力破解机制可灵活配置。
✅ 7. 可拓展方向
如果你希望进一步完善这个模块,还可以考虑以下优化方向:
- 支持短信验证码发送接口对接(如阿里云、腾讯云);
- 增加短信发送频率限制;
- 添加图形验证码前置校验;
- 支持短信验证码重发、失效机制;
- 日志记录短信验证码使用情况,便于风控分析。
✅ 8. 小结
通过新增 SmsGranter
登录授权器,我们展示了 mldong 框架如何快速支持新的登录方式。这种插件化设计不仅让核心登录流程保持简洁,也极大地提升了系统的灵活性与可维护性。
无论是 微信小程序授权登录 、手机号授权登录 ,还是本文介绍的 短信验证码登录,都可以非常轻松地接入系统中,为开发者提供极大的便利。
总结
mldong 框架的登录模块设计兼顾了安全性、灵活性、可维护性三大核心要素,是一个非常值得参考的登录系统设计范例。
特性 | 说明 |
---|---|
多授权方式 | 支持 password、sms、wx、oauth2 等多种登录方式 |
插件化设计 | 提供登录前后处理器,便于业务扩展 |
权限绑定 | 自动识别角色权限,返回给前端用于菜单渲染 |
Sa-Token 集成 | 实现统一鉴权管理,支持多端登录、Token 切换 |
日志记录 | 记录登录成功或失败日志,便于审计追踪 |
后续优化建议
- 增加登录失败次数限制机制;
- 支持 JWT 替代 Token;
- 增加设备指纹识别;
- 增加登录行为画像分析;
- 接入 Prometheus、ELK 实现实时监控。
📢 Gitee地址 :gitee.com/mldong/mldo...
如果你觉得这篇文章对你有帮助,也欢迎分享给你的朋友或同事,一起学习交流!