mldong 快速开发框架登录模块设计与实现

前言

在中后台系统开发中,用户登录是整个系统的入口。一个安全、灵活、可扩展的登录模块,不仅能保障系统的安全性,还能为后续功能(如权限控制、日志审计、多端登录)提供坚实的基础。

本文将以开源项目 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)

✅ 2. 登录服务实现 ------ AuthServiceImpl

方法签名:

java 复制代码
@Override
public LoginToken login(LoginParam param)

主要流程如下:

  1. 获取当前请求的 appCodegrantType
  2. 根据用户名/手机号查找用户
  3. 密码验证(MD5 + Salt)
  4. 或者通过 ILoginGranter 动态获取用户信息
  5. 执行登录前处理器([preLogin](gitee.com/mldong/mldo...
  6. 构建 LoginUser 并执行登录后处理器(postLogin
  7. 使用 Sa-Token 登录并生成 Token
  8. 记录登录日志
  9. 返回 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...


如果你觉得这篇文章对你有帮助,也欢迎分享给你的朋友或同事,一起学习交流!

相关推荐
Code季风12 分钟前
SQL关键字三分钟入门:WITH —— 公用表表达式让复杂查询更清晰
java·数据库·sql
二闹22 分钟前
我为什么躺平?因为代码自己会“飞”呀!
spring boot·后端·运营
沿着缘溪奔向大海26 分钟前
蓝牙数据通讯,实现内网电脑访问外网电脑
java·爬虫·python·socket·蓝牙
过期动态27 分钟前
MySQL中的常见运算符
java·数据库·spring boot·mysql·spring cloud·kafka·tomcat
mortimer28 分钟前
一次MySQL大表索引删除之旅:从卡死到表损坏再到迁移
数据库·后端·mysql
想用offer打牌31 分钟前
一站式了解责任链模式
java·后端·设计模式·责任链模式
专注VB编程开发20年33 分钟前
C# .NET多线程异步记录日声,队列LOG
java·开发语言·前端·数据库·c#
加瓦点灯38 分钟前
浅谈Java Introspector:理解与应用 Java Bean 内省机制
后端
京东云开发者1 小时前
由 Mybatis 源码畅谈软件设计(八):从根上理解 Mybatis 二级缓存
java
YU_admin1 小时前
Java:常见算法
java·数据结构·算法