【RuoYi-Cloud项目研究】【ruoyi-auth模块】登录请求(/login)分析

news/2025/2/7 7:56:03/

文章目录

  • 0. 网关如何处理登录请求
  • 1. Controller
    • 1.1. 获取用户信息
    • 1.2. 创建用户的token
  • 2. Service
    • 2.1. FeignClient远程查询用户信息
    • 2.2. 验证密码
  • 3. 何时刷新 token,如何刷新【本文重点】

本文主要是分析登录请求 /login 的过程。

调用过程是:ruoyi-auth —> ruoyi-system

0. 网关如何处理登录请求

登录请求 127.0.0.1:8080/auth/login是特殊的请求。经过的过滤器有:

AuthFilter(order=-200)
XssFilter(order=-100)
CacheRequestFilter(order=1)
ValidateCodeFilter(order=2)
StripPrefix(order=3)

AuthFilter 认证过滤器对需要排除的 uri 地址,不检查是否有 token 直接放行(需要排除的配置可以在nacos中配置)。默认配置如下:

# 安全配置
security:# 验证码captcha:enabled: truetype: math# 防止XSS攻击xss:enabled: trueexcludeUrls:- /system/notice# 不校验白名单ignore:whites:- /auth/logout- /auth/login- /auth/register- /*/v2/api-docs- /csrf

(注意是“认证”不是“鉴权”,认证主要是判断 token 是否有效,不涉及权限)

1. Controller

    @PostMapping("login")public R<?> login(@RequestBody LoginBody form){// 用户登录LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());// 获取登录tokenreturn R.ok(tokenService.createToken(userInfo));}
  • 用户登录
  • 生成 token 返回

1.1. 获取用户信息

请看 Service

1.2. 创建用户的token

  • 创建token返回给前端
/*** 创建令牌*/
public Map<String, Object> createToken(LoginUser loginUser)
{String token = IdUtils.fastUUID();// <1> 封装用户信息Long userId = loginUser.getSysUser().getUserId();String userName = loginUser.getSysUser().getUserName();loginUser.setToken(token);loginUser.setUserid(userId);loginUser.setUsername(userName);loginUser.setIpaddr(IpUtils.getIpAddr());// <2> 刷新tokenrefreshToken(loginUser);// <3> Jwt存储信息Map<String, Object> claimsMap = new HashMap<String, Object>();claimsMap.put(SecurityConstants.USER_KEY, token);claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);// 接口返回信息Map<String, Object> rspMap = new HashMap<String, Object>();rspMap.put("access_token", JwtUtils.createToken(claimsMap));rspMap.put("expires_in", expireTime);return rspMap;
}

<1> 处:

把从 ruoyi-system 模块获取到的用户信息(用户、角色、权限)连同关键部分(uuid、userId、userName)重新封装

<2> 处:

refreshToken方法负责刷新token。何时刷新、如何刷新是一个值得讨论的问题。在本文的第三章重点讨论了这个问题。

<3> 处:

创建 token 的负载信息,把 uuid、userId、userName 作为 token 的负载,来创建 token。并返回给用户

2. Service

    public LoginUser login(String username, String password){...// IP黑名单校验【在哪里初始化黑名单的???】String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())){recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");}// 查询用户信息R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);...passwordService.validate(user, password);return userInfo;}

主要包括 2 个部分。

  • 用 FeignCilent 从远程服务查询用户信息
  • 密码验证

注意: remoteUserService.getUserInfo(username, SecurityConstants.INNER) 的 INNER 指定了该请求是“内部”请求,模块 auth 还会对 INNER 做处理。

2.1. FeignClient远程查询用户信息

以下代码com.ruoyi.system.api.RemoteUserService在 ruoyi-api-system 中,只是定义了相关的 api 并不是实现。RuoYi 抽象了与系统相关的 ruoyi-api-system。在里面写公共的类或接口

@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = RemoteUserFallbackFactory.class)
public interface RemoteUserService
{/*** 通过用户名查询用户信息** @param username 用户名* @param source 请求来源* @return 结果*/@GetMapping("/user/info/{username}")public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

url是:/user/info/{username}。

调用服务是: ruoyi-system。

设置了降级 fallback 处理:RemoteUserFallbackFactory

备注:获取用户信息的详细分析参考:https://www.yuque.com/yuchangyuan/tkb5br/vktircc4smqw8ggs

2.2. 验证密码

  • 验证密码

主要是验证“可重试次数”和“密码正确性”

1、验证重试次数(默认如果错误超过5次就锁定 10分钟)

2、验证密码是否与数据库匹配。如登录成功需要清除密码错误次数。注册和验证密码用到的密码验证器要一致。

3. 何时刷新 token,如何刷新【本文重点】

  • 何时刷新

只有在将要达到过期时间,才会刷新,来续期,通常可以认为是 2/3 的时间点。

① 调用refresh方法刷新:refresh 接口

② 创建 token 时:createToken方法

③ 设置用户信息时:setLoginUser方法

④ 验证 token 时:verifyToken方法

主要是第四点。是通过 mvc 拦截器实现的

  • 如何刷新

1、定时任务方案:

① 拦截用户请求,保存 key=userId,value=需要刷新的时间点到一个全局的 map 中

② 用 quartz 启动一个定时任务间隔一段时间扫描 map 来刷新

缺点:不太好控制

2、拦截器方案:

原理:是通过 mvc 的拦截器 HandlerInterceptor 实现的。

为什么不通过过滤器实现,而是拦截器?

答案:因为我们的目的不是要过滤掉请求,而是拦截请求并根据条件设置token的过期时间

public class HeaderInterceptor implements AsyncHandlerInterceptor
{@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{........String token = SecurityUtils.getToken();if (StringUtils.isNotEmpty(token)){LoginUser loginUser = AuthUtil.getLoginUser(token);if (StringUtils.isNotNull(loginUser)){// 验证和刷新 token 的过期时间AuthUtil.verifyLoginUserExpire(loginUser);SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);}}return true;}
}

如上面的代码,RuoYi 采用的也是拦截器方案,在HeaderInterceptor拦截器中,针对每个请求验证和刷新 token 的过期时间。详情参考:https://www.yuque.com/yuchangyuan/tkb5br/d387fbc5d3f5522fa013b5e087a0dad9


http://www.ppmy.cn/news/1142525.html

相关文章

笔记38:膨胀卷积/空洞卷积的原理

本地笔记&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\膨胀卷积 a a a a a a a a a a a a a a a

QT基础入门——Qt事件(五)

前言&#xff1a; 事件&#xff08;event&#xff09;是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘&#xff0c;或者是窗口需要重新绘制的时候&#xff0c;都会发出一个相应的事件。一些事件在对用户操作做出响应时发出&#xff0c;如键盘事件等&#x…

论文阅读笔记(Clover: 计算与存储被动分离的分布式键值存储系统)

关于Disaggregating Persistent Memory and Controlling Them Remotely: An Exploration of Passive Disaggregated Key-Value Stores这篇论文的笔记 原文链接 提出背景 传统的分布式存储系统中&#xff0c;每个节点都会包含计算和存储两个部分&#xff0c;一个节点既可以访…

这些负载均衡都解决哪些问题?服务、网关、NGINX

这篇文章解答一下群友的一系列提问&#xff1a; 在微服务项目中&#xff0c;有服务的负载均衡、网关的负载均衡、Nginx的负载均衡&#xff0c;这几个负载均衡分别用来解决什么问题呢&#xff1f; 在微服务项目中&#xff0c;服务的负载均衡、网关的负载均衡和Nginx的负载均衡都…

【工作记录】css3 grid布局笔记

概述 Grid 布局即网格布局&#xff0c;是一种新的 CSS 布局模型&#xff0c;比较擅长将一个页面划分为几个主要区域&#xff0c;以及定义这些区域的大小、位置、层次等关系。号称是最强大的的 CSS 布局方案&#xff0c;是目前唯一一种 CSS 二维布局 参数配置说明 属性说明可…

ubuntu配置vscode c++环境

下载vscode deb安装包 Get Started with C on Linux in Visual Studio Code 1. 安装vscode sudo dpkg -i code_1.83.0-1696350811_amd64.deb 2. 确保gcc编译器已经安装 g --version 如果没有安装&#xff0c;执行以下命令安装 sudo apt-get update sudo apt-get install …

JS进阶-深浅拷贝

浅拷贝和深拷贝只针对引用类型 浅拷贝 浅拷贝&#xff1a;拷贝的是地址 常见方法&#xff1a; 1.拷贝对象&#xff1a;Object.assgin()/展开运算符{...obj}拷贝对象 2.拷贝数组&#xff1a;Array.prototype.concat()或者[...arr] 如果是简单数据类型拷贝值&#xff0c;引…

照片处理软件Lightroom Classic mac中文版功能介绍(Lrc2021)

Lightroom Classic 2022 mac是一款桌面编辑工具&#xff0c;lrc2021 mac包括提亮颜色、使灰暗的摄影更加生动、删除瑕疵、将弯曲的画面拉直等。您可以在电脑桌面上轻松整理所有照片。使用Lightroom Classic&#xff0c; 轻松整理编辑照片&#xff0c;为您的作品锦上添花。 Ligh…