从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是封装了加密方法的,所以不用我们进行加密操作。

相关推荐
hhw19911230 分钟前
c#面试题整理6
java·开发语言·c#
程序视点1 小时前
SpringBoot配置入门
java·spring boot·spring
Benaso2 小时前
Java,Golang,Rust 泛型的大体对比小记
java·golang·rust
程序员清风2 小时前
什么时候会考虑用联合索引?如果只有一个条件查就没有建联合索引的必要了么?
java·后端·面试
Seven972 小时前
【设计模式】掌握建造者模式:如何优雅地解决复杂对象创建难题?
java·后端·设计模式
自在如风。3 小时前
MyBatis-Plus 使用技巧
java·mybatis·mybatis-plus
XORE953 小时前
IDEA Generate POJOs.groovy 踩坑小计 | 生成实体 |groovy报错
java·spring·intellij-idea
heart000_13 小时前
基于SpringBoot的智能问诊系统设计与隐私保护策略
java·spring boot·后端
半聋半瞎3 小时前
【进程和线程】(面试高频考点)
java·jvm·面试
功德+n3 小时前
在 Maven 中使用 <scope> 元素:全面指南
java·maven