后端控制登陆的Controller位置
对应前端文件
二维码
来看看二维码是怎么实现的,点击图片触发了getCode这个函数
这个getCode函数被放在初始化函数里,可以看到具体的getCodeImg函数接收了一个img和uuid,所以我们要提供给前端的也就是uuid和img
调用的是这个函数,而这个函数调用了request的函数来自/utils/request,具体先不管这个函数是怎么实现的,大概意思就是指定一个方法和url就可以发送请求了
来到后端的代码
大体步骤是获取验证码实例--》去除小数情况--》缓存进redis保存,并且设置缓存时间--》返回验证码信息给前端
@ApiOperation("获取用户信息")
@GetMapping(value = "/info")
public ResponseEntity<Object> getUserInfo() {
return ResponseEntity.ok(SecurityUtils.getCurrentUser());
}
@ApiOperation("获取验证码")
@AnonymousGetMapping(value = "/code")//支持匿名访问的get请求
//ResponseEntity是可以添加HttpStatus状态码的HttpEntity的扩展类。被用于RestTemplate和Controller层方法
public ResponseEntity<Object> getCode() {
// 获取运算的结果
Captcha captcha = loginProperties.getCaptcha();//得到一个验证码实例
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();//uuid=properties对应的是jwt+独立的uui
String captchaValue = captcha.text();
//当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型,如果二维码是小数就舍弃掉
//ordinal():用于获取某个枚举对象的位置索引值
if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
captchaValue = captchaValue.split("\\.")[0];
}
// 缓存进redis并且设置过期时间,loginProperties.getLoginCode().getExpiration()是两分钟
redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
// 验证码信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("img", captcha.toBase64());//jwt用到的编码方法
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);//ok表示返回状态码是200
}
几个函数分析
getCaptcha()
其他的登陆功能
前端会传递给后端的参数在data里面,上面的url和method是请求
其中的uuid是验证码的唯一识别码(之前后端给前端发送验证码的时候同样也往redis里存了一份,所以后续在后端会从redis缓存中拿出来校对)
后端的接收的对象里包括了这几个参数
具体的登陆授权代码
@ApiOperation("登录授权")
@AnonymousPostMapping(value = "/login")//支持匿名访问的post请求
//@Validated注解用于校验从前端传递的参数
//@RequestBody将json格式的数据转为java对象,也就是接收前端数据
public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
// 密码解密,使用的私钥具体在配置文件可以看到
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
// 从redis缓存中拿到验证码
String code = (String) redisUtils.get(authUser.getUuid());
// 清除验证码缓存
redisUtils.del(authUser.getUuid());
//判断验证码
if (StringUtils.isBlank(code)) {
throw new BadRequestException("验证码不存在或已过期");
}
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {//验证码不正确
throw new BadRequestException("验证码错误");
}
//security相关的内容
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
//jwtToken规范,不用去服务器端也保存一份token进行校对,只要是没有过期,就可以使用
//获得一个登陆的主题,这里封装了所有的信息
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 保存在线信息,为了看目前有多少人在线
onlineUserService.save(jwtUserDto, token, request);
// 返回 token 与 用户信息
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}};
if (loginProperties.isSingleLogin()) {
//踢掉之前已经登录的token
onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
}
return ResponseEntity.ok(authInfo);
}
RsaProperties.privateKey
回顾整个登陆流程:
进入登陆页面,自动获取验证码,每次生成的验证码都会不一样
后端生成验证码,通过(uuid,值)的键值对放入redis并且设置过期时间为,然后返回uuid,图片,url给前端
前端通过访问auth/code获取后端生成的二维码
用户填写用户名,密码,验证码,点击登陆
前端用预设的公匙进行密码数据加密,发送到后端
后端接收数据后,首先用预设的私匙进行解密,然后从redis中获取验证码,判断是否正确或者过期,然后清除验证码
如果用户的密码和验证码正确就生成token,并保存在线信息,然后返回用户信息和token给前端