从ruoyi学习springsecurity(1)

  • 这是本人写的第一篇技术博客,以此来记录本人学习的心路历程,希望我能坚持下来吧*

这里先推荐一个对初学者学习security比较有帮助的教学视频:www.bilibili.com/video/BV1mm... 本人也是从这里开始学起的。 我们首先看到若依的登录代码

less 复制代码
   @PostMapping("/login")  
public AjaxResult login(@RequestBody LoginBody loginBody)  
{  
AjaxResult ajax = AjaxResult.success();  
// 生成令牌  
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),  
loginBody.getUuid());  
ajax.put(Constants.TOKEN, token);  
return ajax;  
}

我们可以看到若依登录的首先是调用了loginService.login,接下来我们转到login方法

scss 复制代码
public String login(String username, String password, String code, String uuid)  
{  
// 验证码校验  
   validateCaptcha(username, code, uuid);  
// 登录前置校验  
   loginPreCheck(username, password);  
// 用户验证  
   Authentication authentication = null;  
   try  
     {  
     UsernamePasswordAuthenticationToken authenticationToken = new         UsernamePasswordAuthenticationToken(username, password);   
AuthenticationContextHolder.setContext(authenticationToken);  
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername  
authentication = authenticationManager.authenticate(authenticationToken);  
} 
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));  
throw new UserPasswordNotMatchException();  
}  
else  
{  
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));  
throw new ServiceException(e.getMessage());  
}  
}  
finally  
{  
AuthenticationContextHolder.clearContext();  
}  
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));  
LoginUser loginUser = (LoginUser) authentication.getPrincipal();  
recordLoginInfo(loginUser.getUserId());  
// 生成token  
return tokenService.createToken(loginUser);  
}

在跳过一些前置的校验之后 我们直接看到try语句块里的内容,它先是根据输入的username和psw生成了一个token 然后AuthenticationContextHolder.setContext(authenticationToken),将token保存到了内置的Threadlocal中,接下来也就用authenticationManager.authenticate(authenticationToken)方法开始验证。 我们可以看一看上面的教学视频的作者做的一张表

这是security官方所执行的流程,而这里我们所写的login方法也就起到了第一个部分的作用而后经过一系列的内部处理,我们转到了UserDetailsServiceImpl.loadUserByUsername方法,我们现在看一看这个方法

scss 复制代码
@Override  
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException  
{  
SysUser user = userService.selectUserByUserName(username);  
if (StringUtils.isNull(user))  
{  
log.info("登录用户:{} 不存在.", username);  
throw new ServiceException(MessageUtils.message("user.not.exists"));  
}  
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))  
{  
log.info("登录用户:{} 已被删除.", username);  
throw new ServiceException(MessageUtils.message("user.password.delete"));  
}  
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))  
{  
log.info("登录用户:{} 已被停用.", username);  
throw new ServiceException(MessageUtils.message("user.blocked"));  
}  
  
passwordService.validate(user);  
  
return createLoginUser(user);  
}  
  
public UserDetails createLoginUser(SysUser user)  
{  
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));  
}  
}

我们首先是根据token中的username进行一系列的检查之后,直接进行下面的createLoginUser方法,而这里最关键的检查密码passwordService.validate(user),我们将在下文讨论。注意这里和官方做法不同的是我们是先进行密码正确性与否的校验之后才返回了UserDetails,UserDetails内部封装了user的一系列信息。在进行上述一系列的检查之后,(在此期间security内部SecurityContextHolder生成了本账号的一个上下文对象)我们返回到了service.login方法,并生成了token.

public 复制代码
{  
Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();  
String username = usernamePasswordAuthenticationToken.getName();  
String password = usernamePasswordAuthenticationToken.getCredentials().toString();  
  
Integer retryCount = redisCache.getCacheObject(getCacheKey(username));  
  
if (retryCount == null)  
{  
retryCount = 0;  
}  
  
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())  
{  
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,  
MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime)));  
throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime);  
}  
  
if (!matches(user, password))  
{  
retryCount = retryCount + 1;  
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,  
MessageUtils.message("user.password.retry.limit.count", retryCount)));  
redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES);  
throw new UserPasswordNotMatchException();  
}  
else  
{  
clearLoginRecordCache(username);  
}  
}  
  
public boolean matches(SysUser user, String rawPassword)  
{  
return SecurityUtils.matchesPassword(rawPassword, user.getPassword());  
}

密码合法性检查则较为简单,将输入的密码和数据库中的密码进行比较也就可以了 ,这里注意match是封装了加密方法的,所以不用我们进行加密操作。

相关推荐
杨DaB12 分钟前
【SpringBoot】Swagger 接口工具
java·spring boot·后端·restful·swagger
YA33313 分钟前
java基础(九)sql基础及索引
java·开发语言·sql
桦说编程33 分钟前
方法一定要有返回值 \ o /
java·后端·函数式编程
小李是个程序1 小时前
登录与登录校验:Web安全核心解析
java·spring·web安全·jwt·cookie
David爱编程1 小时前
Java 创建线程的4种姿势,哪种才是企业级项目的最佳实践?
java·后端
hrrrrb2 小时前
【Java Web 快速入门】十一、Spring Boot 原理
java·前端·spring boot
Java微观世界2 小时前
Object核心类深度剖析
java·后端
MrSYJ2 小时前
为什么HttpSecurity会初始化创建两次
java·后端·程序员
hinotoyk2 小时前
TimeUnit源码分享
java
AAA修煤气灶刘哥3 小时前
Java+AI 驱动的体检报告智能解析:从 PDF 提取到数据落地全指南
java·人工智能·后端