目录
一、登录流程设计
二、代码详细设计
1. 获取验证码
1) 表达式型验证码
2) 特殊字符型验证码
3) 中文验证码
2. 登录验证密码和验证码
3. 注销
随着互联网技术的更新迭代,越来越多的应用采用手机验证码的方式登录,更快捷、安全。
现在仍有很多应用采用验证码的模式设计登录系统,不需要手机号注册,也能有效地提升系统的安全性,即使你的账号用户名和密码泄露出去了,但是如果不获取验证码并输入正确验证码的情况下,还是无法进入到系统,也能有效地抵御一些恶意攻击的手段,因为破解验证码相当于多加了一道门槛。
本文就图片验证码登录方式实现详细说明, 其中图片验证码主要有运算表达式型验证码、特殊字符验证码等。
一、登录流程设计
1. 请求进入到系统。
2. 判断该请求是否携带token。前端获取到请求, 如果有token,那么尝试携带token发请求给后端请求用户信息。
3. 判断token是否有效。前端get到用户信息,进入系统,get不到用户信息表示token已经失效,需要进入到重新申请token的流程。
4. 请求验证码。没有token 那么需要进入到生成验证码步骤,进入到登录页面时,前端给后端发一个http请求:
http://localhost:8080/picture。
5. 生成验证码。后端接收到请求后,处理请求, 以下三步相当于并列执行:
1) 使用验证码工具类生成验证码表达式,并生成表达式的结果,将表达式以base64的形式返回给前端。
2) 将表达式的结果存入到本地缓存里并设置失效时间,该结果即为前端所填的验证码值,后续为login匹配做准备, 此处可以使用redis缓存。
3) 生成32位uuid返回给前端,同时后端可以记录请求的ID。
6. 验证验证码。前端发起login请求,携带用户名、密码、验证码和uuid(获取验证码时返回的uuid),后端先比较验证码,此处为什么可以先比较验证码?
理由有二:
1) 获取验证码时,给某个请求标记了一个uuid, 通过比较uuid和code能确认唯一用户。
2) 验证码是存在内存里,速度也快,缓存的作用体现出来了,验证码比较通过后再比较用户名和密码。
3) 用户名和密码通过后,返回给前端login成功的status,后端并将token写入到session里,同时需要给token设置失效时间,这样前端就能从cookie里根据key拿到服务器session里的token了。
7. 注销登录。前端发起注销登录请求, 清除当前用户的token缓存信息,后端同时清除掉session, 前端重新发起请求,刷新登录页面的验证码。
用processon 画了一个登录、验证、注销的流程图:
二、代码详细设计
借助开源的图形验证码工具箱easy-captcha,引入pom依赖:
com.github.whvcse
easy-captcha
1.6.2
1. 获取验证码
1) 表达式型验证码
存入缓存的格式 : key: uuid, value: verCode 。
@GetMapping("/picture")
@ResponseBody
public Response getPicture() {
ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(150, 50);
// 设置表达式的运算位数
arithmeticCaptcha .setLen(2);
// 运算表达式:1+1=?,运算结果存放在.text()方法的返回值里。
String verCode = arithmeticCaptcha.text().toLowerCase();
String uuid = UUIDUtil.simpleUUID();
log.info("当前uuid:{},验证码:{}", uid, verCode);
// 单位:秒/s
LFUCache.put(uid, verCode, 60);
return Response.ok(MapUtilJson.build("uuid", uid).build("image", arithmeticCaptcha .tobase64()));
}
返回给前端json格式长这样:
{
"code": "0",
"msg": "操作成功",
"data": {
"uuid": "db9f05fb0a1640c0b2cfe0a58e15308e",
"image": "data:image/png;base64,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"
},
"requestId": "92d8bdb5-5609-470f-ac4b-91161954a3f9",
"success": true
}
2) 特殊字符型验证码
特殊符号验证码采用SpecCaptcha类实现。
SpecCaptcha captcha = new SpecCaptcha(150, 50, 5);
3) 中文验证码
中文验证码还是闪动的,如下的例子其实是3个字母。
ChineseGifCaptcha captcha = new ChineseGifCaptcha(150, 50, 4);
2. 登录验证密码和验证码
后端定义/login接口,验证验证码、用户名和密码,通过后将用户信息设置到session里, key 为currentUser。
@PostMapping("/login")
@ApiOperation(value = "登录")
public Response login(@Validated @RequestBody LoginDto loginDto) {
// 比较验证码
String verCode = LFUCache.get(loginDto.getUuid());
if (!StrUtil.equalsIgnoreCase(verCode , loginDto.getCaptchaCode())) {
return Response.er("500", "验证码不正确或已失效!");
}
// 比较用户名
String pwd= ScecrtUtil.sha256(loginDto.getPassword());
SysUser sysUser = sysUserService.queryUser(loginDto.getUsername(),pwd);
if (sysUser == null) {
return Response.er("501", "用户名或密码不正确");
}
// 如果有权限,那么可获取用户权限,并设置到session里。
return Response.ok(SessionUtil.setUserInfo(sysUser));
}
session 的id 格式为: "token: login:session: 1: 随机数", 其中1为用户id,生成token的形式采用对 "token: login:session: 1 : 随机整数(10000范围以内的4位数)" 可逆的方式加密, 这样能保证每次登录请求生成的token 是不同的; 当前端从cookie里把token拿出来后,根据当前用户id 去比较session里的token, 如果token 匹配成功,那么能继续后续访问,如果匹配失败,那么需要重新生成token并写入到session和cookie里。
1) session.set("token: login:session: 1: 9999","0ad7ed338e364cbe806e0b0a6ea3d181")
2) cookie.set("token","0ad7ed338e364cbe806e0b0a6ea3d181");
3. 注销
前端会携带token 放在requestHeader里发起注销请求: /logout。请求头为:
token = 0ad7ed338e364cbe806e0b0a6ea3d181
1) 在后端根据cookieName的值'token'来删除cookie即可。
2) 根据 token 的value 解密获取到指定的用户id,然后根据用户id删除掉对应session的value值。
3) 如果token 存缓存里,那么需要从缓存里移除掉。