若依微服务版登录流程源码分析2
创始人
2024-04-22 01:32:39
0

接上篇,后端接收到“/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中的keyString 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 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 filter(ServerWebExchange exchange, GatewayFilterChain chain)
{ServerHttpRequest request = exchange.getRequest();ServerHttpRequest.Builder mutate = request.mutate();String url = request.getURI().getPath();// 不需要验证的路径直接执行过滤器链,白名单中配置,如loginif (StringUtils.matches(url, ignoreWhite.getWhites())){return chain.filter(exchange);}// 从请求头中获取tokenString token = getToken(request);if (StringUtils.isEmpty(token)){return unauthorizedResponse(exchange, "令牌不能为空");}// 从令牌中获取数据声明Claims claims = JwtUtils.parseToken(token);if (claims == null){return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");}// 从令牌中获取用户标识,userkey即为登录时作为token的uuidString 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执行具体的业务逻辑了,到此若依的登录流程源码分析完毕

相关内容

热门资讯

电视安卓系统哪个品牌好,哪家品... 你有没有想过,家里的电视是不是该升级换代了呢?现在市面上电视品牌琳琅满目,各种操作系统也是让人眼花缭...
安卓会员管理系统怎么用,提升服... 你有没有想过,手机里那些你爱不释手的APP,背后其实有个强大的会员管理系统在默默支持呢?没错,就是那...
安卓系统软件使用技巧,解锁软件... 你有没有发现,用安卓手机的时候,总有一些小技巧能让你玩得更溜?别小看了这些小细节,它们可是能让你的手...
安卓系统提示音替换 你知道吗?手机里那个时不时响起的提示音,有时候真的能让人心情大好,有时候又让人抓狂不已。今天,就让我...
安卓开机不了系统更新 手机突然开不了机,系统更新还卡在那里,这可真是让人头疼的问题啊!你是不是也遇到了这种情况?别急,今天...
安卓系统中微信视频,安卓系统下... 你有没有发现,现在用手机聊天,视频通话简直成了标配!尤其是咱们安卓系统的小伙伴们,微信视频功能更是用...
安卓系统是服务器,服务器端的智... 你知道吗?在科技的世界里,安卓系统可是个超级明星呢!它不仅仅是个手机操作系统,竟然还能成为服务器的得...
pc电脑安卓系统下载软件,轻松... 你有没有想过,你的PC电脑上安装了安卓系统,是不是瞬间觉得世界都大不一样了呢?没错,就是那种“一机在...
电影院购票系统安卓,便捷观影新... 你有没有想过,在繁忙的生活中,一部好电影就像是一剂强心针,能瞬间让你放松心情?而我今天要和你分享的,...
安卓系统可以写程序? 你有没有想过,安卓系统竟然也能写程序呢?没错,你没听错!这个我们日常使用的智能手机操作系统,竟然有着...
安卓系统架构书籍推荐,权威书籍... 你有没有想过,想要深入了解安卓系统架构,却不知道从何下手?别急,今天我就要给你推荐几本超级实用的书籍...
安卓系统看到的炸弹,技术解析与... 安卓系统看到的炸弹——揭秘手机中的隐形威胁在数字化时代,智能手机已经成为我们生活中不可或缺的一部分。...
鸿蒙系统有安卓文件,畅享多平台... 你知道吗?最近在科技圈里,有个大新闻可是闹得沸沸扬扬的,那就是鸿蒙系统竟然有了安卓文件!是不是觉得有...
宝马安卓车机系统切换,驾驭未来... 你有没有发现,现在的汽车越来越智能了?尤其是那些豪华品牌,比如宝马,它们的内饰里那个大屏幕,简直就像...
p30退回安卓系统 你有没有听说最近P30的用户们都在忙活一件大事?没错,就是他们的手机要退回安卓系统啦!这可不是一个简...
oppoa57安卓原生系统,原... 你有没有发现,最近OPPO A57这款手机在安卓原生系统上的表现真是让人眼前一亮呢?今天,就让我带你...
安卓系统输入法联想,安卓系统输... 你有没有发现,手机上的输入法真的是个神奇的小助手呢?尤其是安卓系统的输入法,简直就是智能生活的点睛之...
怎么进入安卓刷机系统,安卓刷机... 亲爱的手机控们,你是否曾对安卓手机的刷机系统充满好奇?想要解锁手机潜能,体验全新的系统魅力?别急,今...
安卓系统程序有病毒 你知道吗?在这个数字化时代,手机已经成了我们生活中不可或缺的好伙伴。但是,你知道吗?即使是安卓系统,...
奥迪中控安卓系统下载,畅享智能... 你有没有发现,现在汽车的中控系统越来越智能了?尤其是奥迪这种豪华品牌,他们的中控系统简直就是科技与艺...