- 这是本人写的第一篇技术博客,以此来记录本人学习的心路历程,希望我能坚持下来吧*
这里先推荐一个对初学者学习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是封装了加密方法的,所以不用我们进行加密操作。