目录

一.Ruoyi管理系统登录整体流程

二.Ruoyi管理系统登录功能调用瀑布图

三.详细登录步骤分解

1.用户访问前端页面

 2. @PostMapping("/login") —— 登录入口

3.前端请求:用户访问登录页时,前端自动发起验证码请求(接口/captchaImage)。

3.1基础配置与生成 UUID 与 Redis 存储键

3.2生成验证码

3.3存储验证码答案到 Redis

 3.4验证码图片转换为 Base64 并返回

4.用户输入并提交

4.1用户输入用户名、密码,填写验证码,点击 “登录” 按钮。前端执行:

4.2处理用户登录请求

4.3验证码校验

4.3.1从系统配置中查询 “是否开启验证码功能”,决定是否执行后续校验逻辑。

4.3.2 拼接 Redis 中验证码的存储键,取出Redis中的验证码答案

4.3.3校验验证码

4.4对用户名、密码进行基础合法性校验

4.5身份认证

4.1执行核心身份认证

4.6异常捕获与处理

        1.异常捕获与处理

2.资源清理(finally 块)

3.认证成功:记录登录日志

4.生成 JWT 令牌并返回

4.7登录流程图

4.8退出登录

4.8.1创建退出登录过滤器(LogoutFilter),该过滤器是处理退出登录请求的核心组件。

一.Ruoyi管理系统登录整体流程

若依(Ruoyi)管理系统的登录流程是其权限控制体系的核心环节,整体基于前后端分离 + Spring Security + JWT + Redis技术栈实现。

二.Ruoyi管理系统登录功能调用瀑布图

三.详细登录步骤分解

1.用户访问前端页面

  • 用户打开浏览器,访问前端地址(如 http://localhost:80)。
  • 前端路由检测到未登录(无 token),自动跳转至 登录页/login)。

 2. @PostMapping("/login") —— 登录入口

3.前端请求:用户访问登录页时,前端自动发起验证码请求(接口/captchaImage)。

3.1基础配置与生成 UUID 与 Redis 存储键
  • IdUtils.simpleUUID():若依工具类生成 UUID,确保每个验证码有唯一标识。
  • CacheConstants.CAPTCHA_CODE_KEY:常量定义为"captcha_codes:",作为 Redis 中存储验证码的前缀,便于统一管理。

3.2生成验证码

3.3存储验证码答案到 Redis
  • 将验证码答案code存入 Redis,键为verifyKeycaptcha_codes:uuid),过期时间为Constants.CAPTCHA_EXPIRATION(默认 2 分钟),避免验证码长期有效被滥用。
 redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
 3.4验证码图片转换为 Base64 并返回
  • Base64 编码:将图片转为字符串,便于前端通过data:image/jpg;base64,xxx直接展示,无需单独请求图片资源。
  • 前端接收后:展示img对应的图片,并保存uuid,登录时随用户名、密码一起提交,用于后端校验。

4.用户输入并提交

4.1用户输入用户名、密码,填写验证码,点击 “登录” 按钮。前端执行:
  • 表单校验(如非空检查)。
  • 封装登录参数为 JSON(包含usernamepasswordcode(用户输入的验证码)、uuid(之前获取的 UUID))。
  • 发送 POST 请求到/login接口,携带上述参数。

4.2处理用户登录请求

4.3验证码校验

主要代码:

public void validateCaptcha(String username, String code, String uuid)
    {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled)
        {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
            String captcha = redisCache.getCacheObject(verifyKey);
            if (captcha == null)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            redisCache.deleteObject(verifyKey);
            if (!code.equalsIgnoreCase(captcha))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }
4.3.1从系统配置中查询 “是否开启验证码功能”,决定是否执行后续校验逻辑。
 boolean captchaEnabled = configService.selectCaptchaEnabled();
4.3.2 拼接 Redis 中验证码的存储键,取出Redis中的验证码答案
  1.  生成 Redis 中存储验证码答案的 “唯一键”,用于后续查询验证码
  2.  从 Redis 获取存储的验证码答案
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
4.3.3校验验证码

4.4对用户名、密码进行基础合法性校验

主要代码:

  public void loginPreCheck(String username, String password)
    {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

4.5身份认证
  1. 封装未认证令牌,将用户输入的 username(用户名)和 password(密码)封装为 Spring Security 标准的未认证令牌。

  2. 存储令牌到线程上下文,解决多线程环境下的 “认证信息传递” 问题。

4.1执行核心身份认证

会调用UserDetailsServiceImpl.loadUserByUsername

该方法是 Spring Security 认证流程的 “数据源入口”,通过 **“查询用户→校验状态→封装权限”** 的逻辑,确保只有 “存在、未删除、未禁用、密码合规” 的用户才能进入后续的密码校验环节,是系统身份认证安全性的第一道保障。同时,LoginUser 对用户权限的封装,也为后续的菜单动态加载、接口权限校验奠定了基础。

  • createLoginUser 方法:将 SysUser 实体转换为若依自定义的 LoginUser 类(实现 UserDetails 接口)。

4.6异常捕获与处理
        1.异常捕获与处理
catch (Exception e) {
    if (e instanceof BadCredentialsException) {
        // 密码错误或用户不存在的处理
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(
            username, 
            Constants.LOGIN_FAIL, 
            MessageUtils.message("user.password.not.match") // 国际化消息:用户名或密码错误
        ));
        throw new UserPasswordNotMatchException(); // 抛出密码不匹配自定义异常
    } else {
        // 其他认证异常(如账号禁用、锁定等)
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(
            username, 
            Constants.LOGIN_FAIL, 
            e.getMessage() // 记录具体异常信息
        ));
        throw new ServiceException(e.getMessage()); // 抛出通用业务异常
    }
}
2.资源清理(finally 块)
  • 作用:无论认证成功或失败,强制清除线程上下文(AuthenticationContextHolder)中存储的认证令牌。
finally {
    AuthenticationContextHolder.clearContext();
}
3.认证成功:记录登录日志

作用:认证成功后,异步记录登录成功日志到sys_login_info表。

AsyncManager.me().execute(AsyncFactory.recordLogininfor(
    username, 
    Constants.LOGIN_SUCCESS, 
    MessageUtils.message("user.login.success") // 国际化消息:登录成功
));
4.生成 JWT 令牌并返回
  • 作用:调用TokenService生成 JWT 令牌,作为用户后续访问接口的身份凭证。
return tokenService.createToken(loginUser);

若依(Ruoyi)系统登录成功后返回的接口响应数据

4.7登录流程图

4.8退出登录

点击退出按钮,前端发起退出登录请求的接口地址。其中/dev-api是前端配置的后端接口代理前缀(用于解决跨域问题),真实后端接口路径是/logout

通过 Spring Security 的 logout() 方法配置退出登录的核心规则,包括:

  • 退出登录的接口地址(前端调用的退出接口)。
  • 退出成功后如何处理(返回响应、清理资源等)。

这里的customLogoutSuccessHandler就是包含onLogoutSuccess方法的类实例。

4.8.1创建退出登录过滤器(LogoutFilter,该过滤器是处理退出登录请求的核心组件。

当用户访问/logout时,LogoutFilter会触发退出逻辑,最终调用logoutSuccessHandleronLogoutSuccess方法。

4.8.2自定义退出登录成功处理器的核心实现

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐