大家好本篇文章带你从头到尾吃透 Ruoyi 登录体系包含验证码生成、登录校验、退出登录三大核心流程前后端代码都配上详细注释小白也能轻松看懂。一、验证码流程前后端完整解析核心思路后端生成验证码图片字符 / 数学公式验证码答案存入 Redis图片转 Base64 返回前端登录时前端提交验证码 UUID后端从 Redis 取出答案进行校验。前端代码Vue// 页面加载时自动获取验证码 created() { this.getCode(); // 获取验证码 this.getCookie(); // 记住账号密码 }, methods: { // 获取验证码图片 getCode() { // 调用封装好的接口请求 getCodeImg().then(res { // 验证码开关后端返回是否开启验证码 this.captchaOnOff res.captchaOnOff undefined ? true : res.captchaOnOff; if (this.captchaOnOff) { // 拼接 Base64 图片前缀显示验证码 this.codeUrl data:image/gif;base64, res.img; // 保存后端返回的 uuid登录时一起提交 this.loginForm.uuid res.uuid; } }); } }请求路径与代理前端请求/dev-api/captchaImage 通过 vue.config.js 代理转发到http://localhost:8080/captchaImage后端代码SpringBootGetMapping(/captchaImage) public AjaxResult getCode(HttpServletResponse response) throws IOException { // 1. 创建成功返回对象用于封装返回数据 AjaxResult ajax AjaxResult.success(); // 2. 查询系统是否开启验证码从数据库配置读取 boolean captchaOnOff configService.selectCaptchaOnOff(); ajax.put(captchaOnOff, captchaOnOff); // 3. 如果关闭验证码直接返回 if (!captchaOnOff) { return ajax; } // 4. 生成唯一 uuid作为 Redis 键的一部分 String uuid IdUtils.simpleUUID(); String verifyKey Constants.CAPTCHA_CODE_KEY uuid; String capStr null, code null; BufferedImage image null; // 5. 判断验证码类型数学公式 / 字符验证码 String captchaType RuoYiConfig.getCaptchaType(); if (math.equals(captchaType)) { // 数学公式如 12?3 String capText captchaProducerMath.createText(); capStr capText.substring(0, capText.lastIndexOf()); // 题干 code capText.substring(capText.lastIndexOf() 1); // 答案 image captchaProducerMath.createImage(capStr); } else if (char.equals(captchaType)) { // 字符验证码如 xY7z capStr code captchaProducer.createText(); image captchaProducer.createImage(capStr); } // 6. 将验证码答案存入 Redis设置过期时间 redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 7. 图片转字节数组 → Base64 FastByteArrayOutputStream os new FastByteArrayOutputStream(); ImageIO.write(image, jpg, os); // 8. 把 uuid 和 Base64 图片返回前端 ajax.put(uuid, uuid); ajax.put(img, Base64.encode(os.toByteArray())); return ajax; }二、登录流程前后端完整解析核心步骤校验验证码Redis 对比校验用户名密码Spring Security生成 Token 返回前端记录登录日志与登录信息前端登录代码// 登录按钮点击事件 handleLogin() { this.$refs.loginForm.validate(valid { if (valid) { this.loading true; // 触发 vuex 登录 action this.$store.dispatch(Login, this.loginForm).then(() { // 登录成功跳首页 this.$router.push({ path: / }); }).catch(() { // 登录失败刷新验证码 this.getCode(); this.loading false; }); } }); }// src/store/user.js 登录逻辑 actions: { Login({ commit }, userInfo) { // 解构前端提交的表单数据 const username userInfo.username.trim(); const password userInfo.password; const code userInfo.code; const uuid userInfo.uuid; return new Promise((resolve, reject) { // 调用登录接口 login(username, password, code, uuid).then(res { // 登录成功保存 token setToken(res.token); commit(SET_TOKEN, res.token); resolve(); }).catch(error { reject(error); }); }); } }// src/api/login.js 接口封装 export function login(username, password, code, uuid) { const data { username, password, code, uuid }; return request({ url: /login, // 登录接口 headers: { isToken: false }, // 登录不需要带 token method: post, // 请求方式 data: data // 提交参数 }); }后端登录核心代码PostMapping(/login) public AjaxResult login(RequestBody LoginBody loginBody) { AjaxResult ajax AjaxResult.success(); // 调用业务层登录方法返回 token String token loginService.login( loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid() ); // 将 token 返回前端 ajax.put(Constants.TOKEN, token); return ajax; }// 登录核心方法 public String login(String username, String password, String code, String uuid) { // 1. 判断是否开启验证码 boolean captchaOnOff configService.selectCaptchaOnOff(); if (captchaOnOff) { // 开启则校验验证码 validateCaptcha(username, code, uuid); } Authentication authentication null; try { // 2. Spring Security 校验账号密码 authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) ); } catch (BadCredentialsException e) { // 密码错误 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, 密码错误)); throw new UserPasswordNotMatchException(); } catch (Exception e) { // 其他登录异常 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } // 3. 登录成功记录日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, 登录成功)); // 4. 获取登录用户信息 LoginUser loginUser (LoginUser) authentication.getPrincipal(); // 5. 记录登录IP、登录时间 recordLoginInfo(loginUser.getUserId()); // 6. 生成 token 返回 return tokenService.createToken(loginUser); }// 验证码校验方法 public void validateCaptcha(String username, String code, String uuid) { // 拼接 Redis key String verifyKey Constants.CAPTCHA_CODE_KEY StringUtils.nvl(uuid, ); // 从 Redis 获取正确验证码 String captcha redisCache.getCacheObject(verifyKey); // 校验完立即删除一次性验证码 redisCache.deleteObject(verifyKey); if (captcha null) { // 验证码过期 throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { // 验证码错误 throw new CaptchaException(); } }// 记录登录信息IP、时间 public void recordLoginInfo(Long userId) { SysUser sysUser new SysUser(); sysUser.setUserId(userId); sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); sysUser.setLoginDate(DateUtils.getNowDate()); userService.updateUserProfile(sysUser); }三、退出登录流程前后端完整解析核心思路前端调用退出接口 → 后端根据 Token 删除 Redis 中用户登录信息 → Token 失效 → 前端清除 Token 并跳回登录页。前端退出代码// src/store/user.js 退出登录 Logout({ commit, state }) { return new Promise((resolve, reject) { // 调用退出接口 logout(state.token).then(() { // 清除本地 token removeToken(); commit(SET_TOKEN, ); resolve(); }).catch(error { reject(error); }); }); }// src/api/login.js 退出接口 export function logout() { return request({ url: /logout, // 退出接口 method: post }); }后端退出代码PostMapping(/logout) public AjaxResult logout() { // 1. 获取当前登录用户信息 LoginUser loginUser SecurityUtils.getLoginUser(); // 2. 删除 Redis 中的登录信息 redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY loginUser.getToken()); // 3. 记录退出日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor( loginUser.getUsername(), Constants.LOGOUT, 退出成功 )); return AjaxResult.success(); }四、完整流程总结一张图记住验证码后端生成图片与答案 → 答案存 Redis → 前端展示 Base64 图片登录前端提交账号 密码 验证码 UUID → 后端校验验证码 → 校验账号密码 → 生成 Token → 返回前端退出前端调用 logout → 后端删除 Redis 用户信息 → Token 失效 → 跳登录页五、总结Ruoyi 的登录体系是非常标准的前后端分离登录方案用 Redis 做验证码与 Token 存储用 Spring Security 做安全认证用 Token 做身份凭证用 Base64 传输验证码图片泳道图