jeecg库(版本jeecg-boot-v3.5.1last)实现了用户登录功能,二开时为了借鉴jeecg用户登录的方法,跑了一遍登录方法:
org.jeecg.modules.system.controller.LoginController#login
定义这个方法的类的路径是:
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/LoginController.java
截图:
login方法代码:
@ApiOperation("登录接口")
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result<JSONObject> login(@RequestBody SysLoginModel sysLoginModel){
Result<JSONObject> result = new Result<JSONObject>();
String username = sysLoginModel.getUsername();
String password = sysLoginModel.getPassword();
//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
if(isLoginFailOvertimes(username)){
return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!");
}
//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
//update-begin--Author:scott Date:20190805 for:暂时注释掉密码加密逻辑,有点问题
//前端密码加密,后端进行密码解密
//password = AesEncryptUtil.desEncrypt(sysLoginModel.getPassword().replaceAll("%2B", "\\+")).trim();//密码解密
//update-begin--Author:scott Date:20190805 for:暂时注释掉密码加密逻辑,有点问题
//update-begin-author:taoyan date:20190828 for:校验验证码
String captcha = sysLoginModel.getCaptcha();
if(captcha==null){
result.error500("验证码无效");
return result;
}
String lowerCaseCaptcha = captcha.toLowerCase();
//update-begin-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可
String origin = lowerCaseCaptcha+sysLoginModel.getCheckKey()+jeecgBaseConfig.getSignatureSecret();
String realKey = Md5Util.md5Encode(origin, "utf-8");
//update-end-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
Object checkCode = redisUtil.get(realKey);
//当进入登录页时,有一定几率出现验证码错误 #1714
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
log.warn("验证码错误,key= {} , Ui checkCode= {}, Redis checkCode = {}", sysLoginModel.getCheckKey(), lowerCaseCaptcha, checkCode);
result.error500("验证码错误");
// 改成特殊的code 便于前端判断
result.setCode(HttpStatus.PRECONDITION_FAILED.value());
return result;
}
//update-end-author:taoyan date:20190828 for:校验验证码
//1. 校验用户是否有效
//update-begin-author:wangshuai date:20200601 for: 登录代码验证用户是否注销bug,if条件永远为false
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername,username);
SysUser sysUser = sysUserService.getOne(queryWrapper);
//update-end-author:wangshuai date:20200601 for: 登录代码验证用户是否注销bug,if条件永远为false
result = sysUserService.checkUserIsEffective(sysUser);
if(!result.isSuccess()) {
return result;
}
//2. 校验用户名或密码是否正确
String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt());
String syspassword = sysUser.getPassword();
if (!syspassword.equals(userpassword)) {
//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
addLoginFailOvertimes(username);
//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
result.error500("用户名或密码错误");
return result;
}
//用户登录信息
userInfo(sysUser, result);
//update-begin--Author:liusq Date:20210126 for:登录成功,删除redis中的验证码
redisUtil.del(realKey);
//update-begin--Author:liusq Date:20210126 for:登录成功,删除redis中的验证码
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(sysUser, loginUser);
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
//update-end--Author:wangshuai Date:20200714 for:登录日志没有记录人员
return result;
}
看起来非常多,但学过源码分析课就知道,大部分都是准备过程,关键代码不多。
(源码分析课我看的马士兵教育连鹏举老师的)马士兵全套Spring源码深度解析:AOP、IOC、Bean生命周期、循环依赖、事务、SpringBoot自动装配等_哔哩哔哩_bilibili
前三行功能是,创建返回结果空对象,取用户端传递的用户名和密码(原始密码,未加密):
Result<JSONObject> result = new Result<JSONObject>();
String username = sysLoginModel.getUsername();
String password = sysLoginModel.getPassword();
紧接着一个判断语句,看用户是否频繁登录,这里产生了一个支线任务,为了避免本文过于冗长,只走正常登录相关的主线,有精力再研究支线:
if(isLoginFailOvertimes(username)){ return result.error500("该用户登录失败次数过多,请于10分钟后再次登录!"); }
接下来取图片验证码,captcha是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称,判断来登录的是不是爬虫程序。包含一个if(captcha==null)支线。
String captcha = sysLoginModel.getCaptcha(); if(captcha==null){ result.error500("验证码无效"); return result; } String lowerCaseCaptcha = captcha.toLowerCase();
下面是判断用户填写验证码是否正确的过程,代码复杂是因为做了加密处理,然后从redis中取验证码的原始字符串,因为这段代码没有生成验证码的过程,我大胆猜测下,图片验证码是根据redis中的checkCode生成的。后面还有一个验证码错误的支线,有精力可以研究。
String origin =lowerCaseCaptcha+sysLoginModel.getCheckKey()+jeecgBaseConfig.getSignatureSecret();
String realKey = Md5Util.md5Encode(origin, "utf-8");
Object checkCode = redisUtil.get(realKey);
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
log.warn("验证码错误,key= {} , Ui checkCode= {}, Redis checkCode = {}", sysLoginModel.getCheckKey(), lowerCaseCaptcha, checkCode);
result.error500("验证码错误"); // 改成特殊的code 便于前端判断 result.setCode(HttpStatus.PRECONDITION_FAILED.value());
return result; }
login方法代码前一半分析完了,很粗略但正合适,因为这部分只是登录的准备工作,以及一些支线任务,内容很简单,不是login方法的关键点。
下面是核心功能:1. 校验用户是否有效 使用了查询数据库,其中queryWrapper.eq(SysUser::getUsername,username)使用了lambda表达式写法,详见: MybatisPlus:中QueryWrapper<>().lambda使用(条件查询)_.lambda().eq-CSDN博客 更多关于MybatisPlus的知识可以看: 黑马程序员最新MybatisPlus全套视频教程,4小时快速精通mybatis-plus框架_哔哩哔哩_bilibili LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SysUser::getUsername,username); SysUser sysUser = sysUserService.getOne(queryWrapper); result = sysUserService.checkUserIsEffective(sysUser); if(!result.isSuccess()) { return result; }
sysUserService.checkUserIsEffective(sysUser)是确认用户有效性的方法,可以瞟一眼。
org.jeecg.modules.system.service.impl.SysUserServiceImpl#checkUserIsEffective源码:
@Override
public Result<?> checkUserIsEffective(SysUser sysUser) {
Result<?> result = new Result<Object>();
//情况1:根据用户信息查询,该用户不存在
if (sysUser == null) {
result.error500("该用户不存在,请注册");
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
return result;
}
//情况2:根据用户信息查询,该用户已注销
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
if (CommonConstant.DEL_FLAG_1.equals(sysUser.getDelFlag())) {
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
baseCommonService.addLog("用户登录失败,用户名:" + sysUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
result.error500("该用户已注销");
return result;
}
//情况3:根据用户信息查询,该用户已冻结
if (CommonConstant.USER_FREEZE.equals(sysUser.getStatus())) {
baseCommonService.addLog("用户登录失败,用户名:" + sysUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
result.error500("该用户已冻结");
return result;
}
return result;
}
都是简单的逻辑判断,判断过程不需要数据库交互。
回到login方法,核心功能2. 校验用户名或密码是否正确,注意从数据库获取的密码是加密后的,前台提交的密码也需要使用相同的MD5加密方式加密,才能验证是否与数据库存储的密码相等。
String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt()); String syspassword = sysUser.getPassword(); if (!syspassword.equals(userpassword)) { //平台用户登录失败锁定用户 addLoginFailOvertimes(username); //平台用户登录失败锁定用户 result.error500("用户名或密码错误"); return result; }
最后一段执行登录过程,在前面验证都通过后,才完成登录认证过程,包括将登录用户的token存入redis,以便后续请求直接比对token,userInfo(sysUser, result);中执行的就是向redis存入token和其他用户信息的操作。
//用户登录信息 userInfo(sysUser, result); //登录成功,删除redis中的验证码 redisUtil.del(realKey); //登录成功,删除redis中的验证码 redisUtil.del(CommonConstant.LOGIN_FAIL + username); LoginUser loginUser = new LoginUser(); BeanUtils.copyProperties(sysUser, loginUser); baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser); //登录日志没有记录人员 return result;