前言:登录之前的生成验证码的过程在我前面已经讲到,这里就不介绍了。
下面我们来看整个登录过程都有哪些操作。
/**
* 登录
* @param loginBody
* @return
*/
@RequestMapping("/login")
public AjaxResult loginUser(@RequestBody LoginBody loginBody){
AjaxResult ajax = AjaxResult.success();
//生成token
String token = loginService.login(loginBody.getUsername(),
loginBody.getPassword(),loginBody.getCode(),loginBody.getUuid());
ajax.put("token",token);
return ajax;
}
我们可以看出,登录的几乎所有逻辑都在login()方法中,其中我将它分为两部分来讲:
第一部分:对于验证码的验证;
第二部分:用户权限的验证;
1.验证码的验证过程
1.1 查看验证码的状态(-true 开 -false 关)
boolean enabled = configService.selectCaptchaEnabled();
public boolean selectCaptchaEnabled() {
String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled");
//假如redis缓存查不到,则开启验证码,保证安全性
if(StringUtils.isEmpty(captchaEnabled)){
return true;
}
//return语句不是若依框架的,原框架是对返回结果进行封装,意思都是一样的。
return captchaEnabled.equals("yes") ? true : false;
}
当返回结果为true,对验证码进行验证。
1.2 对验证码进行验证
//enabled返回true代表验证码开启,则进行验证码的验证.
if(enabled){
validateCaptcha(username,code,uuid);
}
public void validateCaptcha(String username, String code, String uuid)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid,"");
String captcha = redisCache.getCacheObject(verifyKey);
redisCache.deleteObject(verifyKey);
if(captcha == null){
//TODO 记录登录信息
}
if(!code.equalsIgnoreCase(captcha)){
//TODO 记录登录信息
}
}
从上边我们可以看出,
1.他先拼接一个String,值为: captcha_codes + uuid,同时调用nvl(就是封装的一个非空判断的方法)进行处理。
2.从redis缓存中通过拼接的key值找到对应的value值
3.删除redis的缓存
4.若在redis中未找到,或者输入验证码错误,则记录登记的信息。
回顾内容:
同时我们也回顾一下当时在生成验证码时,是怎么放key-value的(在第一篇文章有过程的解读)
//首先他是生成uuid,然后和常量值CAPTCHA_CODE_KEY(captcha_codes)进行拼接
//我们对比上面验证的代码就可以看出两个是对应的。
String uuid = IdUtils.simpleUUID();
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
//这是他存入redis中的操作,key为captcha_codes+uuid,value为code(也就是算术表达式的值)
redisCache.setCacheObject(verifyKey,code,CacheConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
2.用户认证
这里采用的是Spring Security框架
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
//通过AuthenticationManager对用户进行认证
authentication = manager.authenticate(authenticationToken);
我通过debug让大家看一下数据的变化:
我们可以看到principal里面只有username的值
经过认证之后,可以发现已经原来的principal已经封装了用户的信息,包括权限。
认证源码流程:
执行过程authenticationManager.authenticate(authenticationToken) -> WebSecurityConfigurerAdapter中的this.delegate.authenticate(authentication); -> ProviderManager中的Authentication authenticate(Authentication authentication) -> provider.authenticate(authentication); -> AbstractUserDetailsAuthenticationProvider中的this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); -> DaoAuthenticationProvider实现了retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) 最终通过 UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); 返回一个UserDetails对象.
最重要的就是最后一步他执行了loadUserByUsername(username),通过username来查询用户信息,返回UserDetails对象。
3.记录用户登录
public void recordLoginInfo(Long userId)
{
SysUser sysUser = new SysUser();
//设置(更新)用户id
sysUser.setUserId(userId);
//更新用户ip地址
sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
//更新用户登录时间
sysUser.setLoginDate(DateUtils.getNowDate());
userService.updateUserProfile(sysUser);
}
4.生成令牌(创建Token)
public String createToken(LoginUser loginUser)
{
//利用uuid生成Token(个人建议:如果是新手,则不用封装,
//直接生成uuid就可,但要利用split去除符号,
//或者更简单可以利用Random生成也可)
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
}
其中setUserAgent方法是为了设置用户的代理信息
public void setUserAgent(LoginUser loginUser)
{
//User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.1.1171 SLBChan/11
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
//ip:127.0.0.1
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
loginUser.setIpaddr(ip);
//内网IP
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
//browser chrome 9
loginUser.setBrowser(userAgent.getBrowser().getName());
//OperatingSystem window 10
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
使用UserAgent需要引入一下依赖:
<!-- 解析客户端操作系统、浏览器等 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
这里是利用ServletHttpRequest来获取消息头里面的"User-Agent"信息。我们可以通过浏览器debug的形式来看一下"User-Agent"里面的内容:
我们可以从中获取到windows版本、浏览器版本等内容。
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
//重新设置最新的时间
loginUser.setLoginTime(System.currentTimeMillis());
//重新设置过期时间
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
private String createToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
利用jwt创建token(带有前缀和密码验证)
令牌前缀为:
login_user_key密码加密规则为:
SignatureAlgorithm.HS512密钥为:
secret: abcdefghijklmnopqrstuvwxyz(在yml文件中可以设置)
5.通过ajax返回给浏览器token
ajax.put(Constants.TOKEN, token);
return ajax;