点击上方蓝色“架构荟萃”关注我们,输入1024,你懂的
需求分析
现在滑块验证码在许多网站逐步流行起来,比如今日头条搜新闻时会提示滑块验证。
一方面,滑块验证对用户体验来说,比较新颖,操作简单。另一方面其安全性相对于图形验证码来说,并没有降低多少。当然没有绝对的安全验证,只是在不断增加攻击者的穿透成本。另外,还可以起到宣传企业愿景和重大历史事件的作用。
最终效果图
关键技术栈
-
SpringCloud
-
FastDFS:分布式文件系统
-
Redis:利用 Go 语言编写的 Codis 作为分布式缓存系统
-
Mysql
-
Docker/K8s 容器平台
设计要点
1、如何生成滑块临时大图以及小图
2、前后端分离,脱离传统 Session 会话机制,如何存储用户会话滑块图片参数
3、最佳滑块验证方式:统一入口网关验证
滑动验证流程分析
1、服务端随机生成小图和带有小图阴影的背景大图,服务端保存随机抠图位置坐标;
2、前端实现滑动交互,将小图拼在大图阴影之上,同时获取用户滑动距离值(一般语序误差范围在 3-5 像素);
3、前端将用户滑动距离值传入服务端,服务端校验误差是否在容许范围内;验证OK,返回登录验证码
4、服务网关(Zuul)登录验证:通过客户端传值验证码与 redis 缓存验证码比较匹配,决定是否登录成功
数据库设计
滑块主表
CREATE TABLE `us_slide` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`rdm_len` int(11) NOT NULL DEFAULT '5' COMMENT '随机图片总数',
`img_prefix` varchar(20) DEFAULT 'login_slide_img_' COMMENT '随机图片默认编号前缀:slide_img_',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
滑块-随机图片表-从表
CREATE TABLE `us_slide_image` (
`img_no` varchar(20) NOT NULL COMMENT '图片编号',
`img_url` varchar(200) NOT NULL COMMENT '图片地址',
`sid` bigint(20) NOT NULL COMMENT '滑块外键ID',
`expire` char(1) DEFAULT '1' COMMENT '1是过期',
PRIMARY KEY (`img_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
滑块-临时图片记录表:用于通过 fastdfs 删除历史临时滑块图片
CREATE TABLE `us_slide_image_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`his_img_url` varchar(200) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=540 DEFAULT CHARSET=utf8;
分布式文件系统设计:FastDFS
FastDFS
是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等。
Maven 依赖
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
</dependency>
参数配置
fdfs:
soTimeout: 30000
connectTimeout: 20000
thumbImage:
width: 150
height: 150
trackerList:
- 127.0.0.1:22122
FastDFSClient 工具类设计
滑块验证相关接口设计
初始化用户登录大小图片接口设计
/slide/v1/opi/initial-image
RecordRunnar 线程
由于早8点-9点用户登录频次最高,为了提高程序并发请求性能,可以利用 Java 线程池异步多线程方式,异步保存临时图片存储记录,然后用于临时图片清理工具(imgcleaner)单独清理。
java">class RecordRunnar implements Runnable {
private String tempImageUrl;
public RecordRunnar(String tempImageUrl) {
this.tempImageUrl = tempImageUrl;
}
@Override
public void run() {
SlideImageRecordEntity record = new SlideImageRecordEntity();
record.setCreateTime(new Date());
record.setHisImgUrl(this.tempImageUrl);
baseMapper.insertImageRecord(record);
}
}
实体设计
@Data
public class SlideEntity implements Serializable {
private Long id;
/**
* 随机图片大小
*/
private Integer rdmLen;
/**
* 默认图片名称默认前缀:slide_img_
*/
private String imgPrefix;
/**
* 图片集合
*/
private List<SlideImageEntity> imgList;
}
@Data
public class SlideImageRecordEntity implements Serializable {
private long id;
// 历史图片地址
private String hisImgUrl;
private Date createTime;
}
生成滑块小图和带阴影的大图关键代码
随机图片宽度参数说明
javascript">String xwCode = java.util.UUID.randomUUID().toString();
redisClient.setex(SiteConst.Cache.PORTAL_LOGIN_IMAGE_XWIDTH + ":" + xwCode, 5 * 60, widthRandom + "");
每次用户登录时,后台通过 redis设置临时随机图片宽度 xwidth 到缓存(类似与session 会话,保存用户会话参数),用于与前端用户滑块移动宽度比对验证。
同时设置临时随机图片宽度参数:xwidth,过期时间为 5 分钟。
随机小图说明
随机生成的小图 BufferedImage 对象通过 base64 算法加密成一个密码串,
java">/**
* 图片转BASE64
*
* @param image
* @return
* @throws IOException
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
imagedata = bao.toByteArray();
Base64 encoder = new Base64();
String BASE64IMAGE = encoder.encodeAsString(imagedata).trim();
// String BASE64IMAGE=encoder.encodeBuffer(imagedata).trim();
BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", "");
return BASE64IMAGE;
}
用于前端页面通过 img 网页标签把 base64 的加密串渲染出来
base64 串渲染格式
<img src="data:image/png;base64, 加密串/>
滑块图片用户拼装宽度校验接口设计
/slide/v1/opi/check-width/{moveLength}/{xwc}
登录验证码
在前端滑块验证通过后,在用户触发登录接口时,需要把请求参数 vc 传入服务网关进行验证码验证。
javascript">//生成验证码
String verfiyCode = java.util.UUID.randomUUID().toString();
redisClient.setex(SiteConst.Cache.PORTAL_VERIFY_CODE + ":" + verfiyCode, 5 * 60, verfiyCode);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("vc", verfiyCode); // 登录验证码
dataMap.put("xw", xWidth); // 实际图片距离
同时设置登录验证码:verfiyCode,过期时间为 5 分钟。
Docker/K8s
一般微服务基于 docker/k8s 容器化部署,所以考虑单个微服务多节点实例部署,需要考虑数据共享问题,所以不管是随机图片 xwidth 以及登录验证码采用redis 替代 session 会话方式改动成本最小。
Zuul 网关验证:利用网关登录接口方法
在校验登录前,先判断滑块登录验证码 vc 参数是否合法。
验证规则是利用客户端传值验证码与缓存验证码值比较,如果不相等,直接返回验证码校验错误,登录失败。
@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST)
public R login(HttpServletRequest request) {
//图形验证码
String vc = request.getParameter("vc");
if (StringUtils.isNotEmpty(vc)) {
//portal:vc
String vcKey = "portal:vc:" + vc;
boolean captcha = authenticationService.captcha(vcKey, vc);
if (!captcha) {
return R.error(ResponseCode.VALIDATION_ERROR);
}
}
}
推荐阅读
-
扛不住 1W+ 并发流量请求,SpringCache 缓存注解真的那么弱?
-
利用java线程池技术,从MySQL往Elasticsearch导入海量数据
-
一文读懂阿里大中台、小前台战略
-
jsp的10年是谁让它如此落幕?
Java 技术经理一枚,头条付费专栏《Spring Cloud Alibaba微服务实战match》作者,擅长微服务&分布式、SpringCloud&SpringBoot、工作流。
后台回复 1024 免费领取微服务、微信小程序、面试等视频资料,扫描上图微信二维码(WooolaDuang)进微信群