这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
前言
本节介绍RuoYi-Vue的ruoyi-admin模块中的系统登录模块SysLoginController 部分的代码,
今天我们讲解一下登录这里的逻辑,RuoYi-Vue使用的是spring-security框架作为权限控制框架。
那么我们就要写好符合spring-security要求的代码,这样才能真正将spring-security用好。
登录代码
public String login(String username, String password, String code, String uuid) {
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关
if (captchaOnOff) {
validateCaptcha(username, code, uuid);
}
// 用户验证
Authentication authentication = null;
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
if (e instanceof BadCredentialsException) {
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());
}
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUser());
// 生成token
return tokenService.createToken(loginUser);
}
验证码开关
实际生产环境,为了防止恶意用户进行不停地登录爆破,我们需要使用验证码来进行登录限制,但是对于测试环境要进行selenium等工具进行压测或者自动化测试时,测试人员需要我们将对应的验证码关闭,那么这个验证码开关就十分有用了,可以在不发版的情况下对验证码进行开启和关闭。
Authentication
Authentication存储了通过了AuthenticationManager.authenticate(Authentication)方法的请求返回的数据(是一个被 SecurityContextHolder管理的thread-local SecurityContext),在上面的代码中我们可以看到是UsernamePasswordAuthenticationToken这个,那么究竟调用的是个什么方法呢,从注释里我们可以看到是在UserDetailsServiceImpl.loadUserByUsername这里,那么spring-security怎么知道要调用这个service里面的这个方法的呢?
是通过SecurityConfig这个类里面的如下代码将对应的userDetailsService注入到身份认证接口里面的
/**
* 身份认证接口
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
对应的可以看到我们必须要实现的接口中的方法loadUserByUsername
实现的方法我们可以自己编写逻辑,返回的对象必须是
UserDetails的子类,我们这里是LoginUser
这个类。
SecurityUtils获取用户数据
/**
* 获取用户
**/
public static LoginUser getLoginUser()
{
try
{
return (LoginUser) getAuthentication().getPrincipal();
}
catch (Exception e)
{
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
}
}
/**
* 获取Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
SecurityContextHolder中的
private static SecurityContextHolderStrategy strategy;
存储了对应的用户信息上下文,默认实现是ThreadLocalSecurityContextHolderStrategy
从下图可以看到,直接使用的是ThreadLocal