若依微服务版登录流程源码分析2

news2024/11/17 15:57:39

接上篇,后端接收到“/code”请求并将其转发至ValidateCodeHandler处理

生成验证码

进入ValidateCodeServiceImpl#createCaptcha

在这里插入图片描述

这块代码比较简单,就不多赘述

/**
* 生成验证码
*/
@Override
public AjaxResult createCaptcha() throws IOException, CaptchaException {
	AjaxResult ajax = AjaxResult.success();
	// 获取配置文件中配置的验证码开关
	boolean captchaEnabled = captchaProperties.getEnabled();
	ajax.put("captchaEnabled", captchaEnabled);
	if (!captchaEnabled) {
		return ajax;
	}

	String uuid = IdUtils.simpleUUID();
	// 生成验证key,作为稍后存到redis中的key
	String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;

	String capStr = null, code = null;
	BufferedImage image = null;

	// 获取配置文件中配置的验证码类型
	String captchaType = captchaProperties.getType();
    // 生成验证码
    if ("math".equals(captchaType)) {
   	 	// 生成验证码表达式和对应值
    	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)) {
    	capStr = code = captchaProducer.createText();
    	image = captchaProducer.createImage(capStr);
    }

    redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
    // 转换流信息写出
    FastByteArrayOutputStream os = new FastByteArrayOutputStream();
    try
    {
    	ImageIO.write(image, "jpg", os);
    }
    catch (IOException e)
    {
    	return AjaxResult.error(e.getMessage());
    }

    ajax.put("uuid", uuid);
    ajax.put("img", Base64.encode(os.toByteArray()));
    return ajax;
}

发送登录请求

前端handleLogin方法,将用户信息放入cookie,调用login.js的login方法,请求路径为“/auth/login”

在这里插入图片描述

在这里插入图片描述

处理登录请求

客户端向 Spring Cloud Gateway 发出请求。如果Gateway Handler Mapping确定请求与路由匹配,则将其发送到Gateway Web Handler 处理程序。此处理程序通过特定于请求的Fliter链运行请求

在filter包下有几个过滤器链,首先会进入AuthFilter,过滤配置文件中配置的白名单,然后进入ValidateCodeFilter校验验证码

在这里插入图片描述

在这里插入图片描述

进入ValidateCodeServiceImpl#checkCaptcha方法,从redis中取出验证码表达式的值和前端传过来的做比对,逻辑较简单

在这里插入图片描述

验证码校验通过,经过一连串的Filter之后会将请求转发到auth模块下的TokenController#login,开始验证用户信息并创建令牌

在这里插入图片描述

进入login方法,经过几个必要的判断后,第28行开始查询用户信息,这里用feign远程调用了ruoyi-modules-system模块下的SysUserController#info接口,然后将密码错误重试日志入库,并将重试次数存入redis,最后保存登录日志

/**
 * 登录
 */
public LoginUser login(String username, String password) throws Exception {
    //前端密码解密
    password = RsaUtils.decryptByPrivateKey(password);
    // 用户名或密码为空 错误
    if (StringUtils.isAnyBlank(username, password))
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
        throw new ServiceException("用户/密码必须填写");
    }
    // 密码如果不在指定范围内 错误
    if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
            || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
        throw new ServiceException("用户密码不在指定范围");
    }
    // 用户名不在指定范围内 错误
    if (username.length() < UserConstants.USERNAME_MIN_LENGTH
            || username.length() > UserConstants.USERNAME_MAX_LENGTH)
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
        throw new ServiceException("用户名不在指定范围");
    }
    // 查询用户信息,feign调用接口
    R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);

    if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
        throw new ServiceException("登录用户:" + username + " 不存在");
    }

    if (R.FAIL == userResult.getCode())
    {
        throw new ServiceException(userResult.getMsg());
    }

    LoginUser userInfo = userResult.getData();
    SysUser user = userResult.getData().getSysUser();
    if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    }
    if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
    {
        recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }
    // 登录账户密码错误次数缓存键名
    passwordService.validate(user, password);
    // 记录登录信息
    recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
    return userInfo;
}

看下查询用户信息的代码

在这里插入图片描述

获取用户权限,admin拥有所有权限,如果是其他角色根据userid查询

在这里插入图片描述

获取用户菜单权限,admin拥有所有权限,其他用户如果有多角色,则要给该用户所属的每个角色设置权限

在这里插入图片描述

login方法执行完后回到TokenController,下一步就是获取登录token,进入具体方法ruoyi-common-security模块下的TokenService#createToken

该方法主要操作:生成随机uuid作为token、刷新令牌有效期、jwt对数据进行加密并返回

在这里插入图片描述

其他代码一目了然,只需要再看下refreshToken方法,将 token 和用户的角色权限信息存储到 redis

在这里插入图片描述

登录功能到这里就结束了,总的来说做了两件事,一是验证用户信息,二是创建token并返回给前端,当前端再发起任意请求时都会携带token到后端,后端将token转化为userId、userName存储到请求头中;根据 token 查询redis缓存中的权限并和目标资源上标注的权限名称做比对,比对成功即鉴权成功。后面这部分我们以若依中获取用户信息为例来走一遍流程

首先还是来到AuthFIlter

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
    ServerHttpRequest request = exchange.getRequest();
    ServerHttpRequest.Builder mutate = request.mutate();

    String url = request.getURI().getPath();
    // 不需要验证的路径直接执行过滤器链,白名单中配置,如login
    if (StringUtils.matches(url, ignoreWhite.getWhites()))
    {
        return chain.filter(exchange);
    }
    // 从请求头中获取token
    String token = getToken(request);
    if (StringUtils.isEmpty(token))
    {
        return unauthorizedResponse(exchange, "令牌不能为空");
    }
    // 从令牌中获取数据声明
    Claims claims = JwtUtils.parseToken(token);
    if (claims == null)
    {
        return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
    }
    // 从令牌中获取用户标识,userkey即为登录时作为token的uuid
    String userkey = JwtUtils.getUserKey(claims);
    // 判断令牌是否过期,getTokenKey方法内拼接login_tokens:uuid,这就是登录时存到redis中的key,value为用户信息
    boolean islogin = redisService.hasKey(getTokenKey(userkey));
    if (!islogin)
    {
        return unauthorizedResponse(exchange, "登录状态已过期");
    }
    // 从数据声明中获取用户信息
    String userid = JwtUtils.getUserId(claims);
    String username = JwtUtils.getUserName(claims);
    if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))
    {
        return unauthorizedResponse(exchange, "令牌验证失败");
    }

    // 设置用户信息到请求,后面拦截器会从请求header中获取token,并根据token去redis中获取user保存到SecurityContextHolder,
    // 也因此我们可以在其他地方用SecurityUtils.getLoginUser()直接获取用户信息
    addHeader(mutate, SecurityConstants.USER_KEY, userkey);
    addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
    addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
    // 内部请求来源参数清除
    removeHeader(mutate, SecurityConstants.FROM_SOURCE);
    return chain.filter(exchange.mutate().request(mutate.build()).build());
}

执行完过滤器之后会进入WebMvcConfig,该类实现了WebMvcConfigurer,WebMvcConfigurer是一个mvc的配置类,我们可以在里面进行自定义拦截器、视图解析器、静态资源处理等操作,附上sprngmvc流程图

在这里插入图片描述

在这里插入图片描述

进入HeaderInterceptor,该类顶级接口是HandlerInterceptor,它是SpringWebMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于处理器进行预处理和后处理

在preHandle方法里从请求头中获取userid,username和userkey保存到SecurityContextHolder,然后获取token,并根据token去redis中获取user,验证用户有效期,最后将完整user对象保存到SecurityContextHolder,SecurityContextHolder中的方法都是静态方法,所以我们后面可以全局获取用户信息

在这里插入图片描述

拦截器执行完之后,接下来就进入controller执行具体的业务逻辑了,到此若依的登录流程源码分析完毕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/82819.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VM 找不到虚拟磁盘 000001.vmdk 一种排查思路

报错内容“ 找不到文件: E:\win10\Windows 10 x64-000001.vmdk 开启此虚拟机需要用到此文件。如果移动了此文件&#xff0c;请提供它的新位置。 ” 它明明就在哪里&#xff0c;却提示找不到&#xff01;气死 这个时候可以打开“vmware.log” 查看报错内容。 比如我的核心报…

从零开始学Java之Java到底是个啥?

全文大约 【 5000】 字&#xff0c;不说废话&#xff0c;只讲可以让你学到技术、明白原理的纯干货&#xff01;文章带有丰富案例及配图、视频&#xff0c;只为让你更好的理解和运用文中的技术概念&#xff0c;给你带来具有足够启迪的教程...... -----------------------------…

基础数据结构链表

链表是一种常见的基础数据结构&#xff0c;结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配&#xff0c;也就是说&#xff0c;链表是一个功能极为强大的数组&#xff0c;他可以在节点中定义多种数据类型&#xff0c;还可以根据需要随意增添&#xff0c;删除&…

论文速递:带重力约束的点云配准(Gravity-constrained point cloud registration)

标题&#xff1a;Gravity-constrained point cloud registration 作者&#xff1a;Vladim ́ır Kubelka, Maxime Vaidis and Franc ̧ois Pomerleau&#xff0c;加拿大拉瓦尔大学 来源&#xff1a;IROS 2022 摘要 视觉和激光SLAM算法从IMU中获益良多。高频率的IMU数据弥补了…

嵌入式:ARM的流水线技术

三级流水线ARM的组织 ARM的3级流水线介绍 到ARM7为止的ARM处理器使用的简单3级流水线分别为 取指级 &#xff1a;读取指令。译码级 &#xff1a;对指令进行译码。占有“译码逻辑”&#xff0c;不占有“数据路径”。执行级 &#xff1a;指令占有“数据路径”&#xff0c;寄存…

拼搏别样的未来,中国社科院与美国杜兰大学金融管理硕士项目助力你的人生旅程

你憧憬中的未来是什么样子呢&#xff1f;我们的人生路程充满了众多可能性&#xff0c;只要努力就会收获自己的别样人生。我们的人生不是单一的色彩&#xff0c;它是五彩斑斓、精彩纷呈的。在每一个阶段的我们所拥有当时的状态并不能代表永远&#xff0c;随着我们的拼搏与奋斗&a…

2022全年度空调十大热门品牌销量榜单

今年空调的销售得到明显改善&#xff0c;尤其是今年夏天全国多地最高气温同比明显提升&#xff0c;且高温的强度和持续时间还具有一定的极端性。随着气温的骤升&#xff0c;空调市场也迅速升温&#xff0c;各终端销售量出现明显增长。 根据鲸参谋数据统计&#xff0c;今年京东平…

“美亚杯”第五届中国电子数据取证大赛答案解析(个人赛)

A C D 分区5为系统分区 A 1073741824*458.29492085140520.96 E A A E D B 无答案 C 搜索各选项&#xff0c;C项搜不到 B C E 使用火绒剑查看进程调用的动态链接库 C 仿真得 B 内存大小为3G&#xff0c;在系统盘根目录过滤大小在2-8G之间的文件 D Win10时间线信息存放的数…

代码随想录算法训练营第六天| 哈希表理论基础 ,242.有效的字母异位词 , 349. 两个数组的交集 ,202. 快乐数,1. 两数之和

代码随想录算法训练营第六天| 哈希表理论基础 &#xff0c;242.有效的字母异位词 &#xff0c; 349. 两个数组的交集 &#xff0c;202. 快乐数&#xff0c;1. 两数之和 哈希表理论基础 建议&#xff1a;大家要了解哈希表的内部实现原理&#xff0c;哈希函数&#xff0c;哈希碰…

PAT 乙级 1078字符串压缩与解压 python

题目 思路&#xff1a; 生成两个函数&#xff1a;压缩与解压 在压缩中利用flag保留前一个字符串 再利用count统计重复出现字母的个数 如果目前遍历到的字母和flag相同&#xff0c;则count1 反之&#xff0c;说明重复结束 在解压中&#xff0c;如果遇到数字&#xff0c;表示需…

一文带你了解 K8s 是如何部署应用的

通过部署一个 Nginx 服务/实例来简单介绍 K8s 部署一个应用的流程。 1、创建 deployment 资源 kubectl create deployment nginx --imagenginx kubectl expose deployment nginx --port80 --typeNodePort2、查看 deployment 资源 kubectl get deployment3、查看节点上的 pod…

安全需求分析

汽车制造业 MES系统 DNC系统 生产 安全域1 管理层 工控安全隔离装置 交换机 安全配置核查系统 HMI 历史数据库 运行监控系统 实时数据库 打印机 过程 安全域2 监控层 工控漏洞扫描系统 安全交换机 工控安全审计系统 工控入侵检测系统 工程师站 A 操作员站 A 实时数据库A 操作员…

【菜鸡读论文】Margin-Mix: Semi-Supervised Learning for Face Expression Recognition

【菜鸡读论文】Margin-Mix: Semi-Supervised Learning for Face Expression Recognition 感觉最近的每天都在见证历史&#xff0c;上海现在也开始全面放开了&#xff0c;很多高校都已经开始遣返了。小伙伴们都回到家了吗&#xff1f; 上周周末太懒了&#xff0c;就没有更新论…

一文教你在SpringBoot中使用Thymeleaf

一文教你在SpringBoot中使用Thymeleaf1.快速使用Thymeleaf2.Thymeleaf快速入门案例3.Thymeleaf基本语法each遍历其他语法1.快速使用Thymeleaf 首先导入Maven依赖&#xff1a; <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thym…

怎么在图片加文字边框?这些方法值得你收藏

当我们在拍完照片以后&#xff0c;一般都会为它进行后期的编辑修图&#xff0c;因此在图片上面添加一些文字信息和边框是必不可少的&#xff0c;这样不仅能使图片变得更加精致&#xff0c;还可以增加它的信息量。那你们知道怎么给图片加上边框和文字吗&#xff1f;别着急&#…

uniapp实战仿写网易云音乐(一)—底部工具栏以及首页轮播图swiper的实现

文章目录前言首页导航栏公共样式的配置首页轮播图最后前言 从本篇文章开始记录uniapp实战仿写网易云音乐项目的过程&#xff0c;主要会写一下关键步骤和难点&#xff0c;本专栏会保持持续更新&#xff0c;并在最后送上源码&#xff0c;感兴趣的可以订阅本专栏。本篇主要实现的…

【C++11多线程】获取异步任务的结果:future、shared_future

文章目录1.std::future1.1 get()1.2 valid()1.3 share()1.4 wait_for()1.4.1 std::future_status::timeout1.4.2 std::future_status::ready1.4.3 std::future_status::deferred2.std::shared_future3.参考资料1.std::future std::future 是个类模板&#xff0c;其源码如下所示…

[附源码]Python计算机毕业设计大学生志愿者管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【论文阅读】社交网络识别影响力最大节点方法综述-05

Vital nodes identification in complex networks识别单个重要节点的方法基于结构信息一、结构中心性&#xff08;Structural centralities&#xff09;1.度中心性&#xff08;基于邻域的中心性&#xff09;2.四阶邻居信息&#xff08;基于邻域的中心性&#xff09;3.ClusterRa…

html个人博客网站模板(源码)

文章目录1.设计来源1.1 首界面1.2 我的文章界面1.2 发表文章界面1.3 文章详细界面2.效果和源码2.1 目录结构2.2 源代码源码下载作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/128287493 html个人博客网站模板 html个人博…