SpringSceurity(4)---短信验证码功能实现
SpringSceurity(4)---短信验证码功能实现
有关SpringSceurity系列之前有写文章
1、SpringSecurity(1)---认证+授权代码实现
3、SpringSceurity(3)---图形验证码功能实现
一、思考
1、设计思路
在获取短信验证码功能和图形验证码还是有很多相似的地方,所以这里在设计获取短信验证的时候,将之前开发好的的图形验证码进一步整合、抽象与重构。
在获取验证码的时候,它们最大的不同在于: 图形验证码是通过接口返回获取給前端。而短信验证码而言是通过第三方API向我们手机推送。
但是它们在登陆的时候就有很大的不同了,对于图形验证码而言验证通过之前就走 UsernamePasswordAuthenticationFilter 过滤器了开始校验用户名密码了。
但对于短信登陆而言,确实也需要先现在短信验证码是否通过,但是一旦通过他是不走 UsernamePasswordAuthenticationFilter,而是通过其它方式查询用户信息来校验
认证已经通过了。
这篇博客只写获取获取短信验证码的功能,不写通过短信验证码登陆的逻辑。
2、重构设计
这里才是最重要的,如何去设计和整合短信验证码和图形验证码的代码,是我们最应该思考的。如何将相似部分抽离出来,然后去实现不相同的部分。
整理后发现不同点主要在于
1、获取验证码。因为对于图形验证码需要有个画布,而短信验证码并不需要,所以它们可以实现同一个接口,来完成不同的逻辑。
2、发送验证码。对于图形验证码来讲只要把验证码返给前端就可以,而短信验证码而言是通过第三方API将验证码发到我们的手机上。
所以这里也可以通过实现统一接口来具体实现不同的方法。
相同部分我可以通过抽象类来完成实现,不同部分可以通过具体的实现类来实现。

AbstractValidateCodeProcessorService 抽象类是用来实现两种验证码可以抽离的部分。ImageCodeProcessorServiceImpl 和
SmsCodeProcessorServiceImpl方法是来实现两种验证码不同的发送方式。
在简单看下时序图可能会更加明白点。

一个接口只有一个方法(processor)就是处理验证码,它其实需要做三件事。
1、获取验证码。2、将验证码存入session。3、将验证码信息通过短信或者图形验证码发送出去。
首先讲生成获取验证码,这里有一个公共接口和两个实现类

对于保存验证码信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象类来完成,都不需要去实现。
对发送验证码信息而言,只需要实现AbstractValidateCodeProcessorService抽象类的send发送验证码接口即可。
整个大致接口设计就是这样,具体的可以通过代码来展示。
二、代码实现
1、验证码属性
短信验证码和图形验证后包含属性有 code 和 expireTime,短信验证码只有这两个属性,而图形验证码还多一个BufferedImage实例对象属性,所以将共同属性进行抽取
,抽取为ValidateCode类,代码如下:
ValidateCode实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidateCode {
private String code;
private LocalDateTime expireTime;
public boolean isExpired() {
return LocalDateTime.now().isAfter(expireTime);
}
}
对于图形验证码而言,除了需要code和过期时间还需要图片的画布,所以继承ValidateCode之后再写自己属性
ImageCode实体
@Data
public class ImageCode extends ValidateCode {
private BufferedImage image;
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
super(code, expireTime);
this.image = image;
}
public ImageCode(BufferedImage image, String code, int expireIn) {
super(code, LocalDateTime.now().plusSeconds(expireIn));
this.image = image;
}
}
对于短信验证码而言,暂时不需要添加自己的属性字段了。
SmsCode实体
public class SmsCode extends ValidateCode {
public SmsCode(String code, LocalDateTime expireTime) {
super(code, expireTime);
}
public SmsCode(String code, int expireIn) {
super(code, LocalDateTime.now().plusSeconds(expireIn));
}
}
2、ValidateCodeProcessor接口
ValidateCodeProcessor接口主要是完成 验证码的生成、保存与发送的完整流程,接口的主要设计如下所示:
ValidateCodeProcessorService接口
public interface ValidateCodeProcessorService {
/**
* 因为现在有两种验证码,所以存放到seesion的key不能一样,所以前缀+具体type
*/
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
/**
* 通过也是 type+CODE_PROCESSOR获取对于的bean
*/
String CODE_PROCESSOR = "CodeProcessorService";
/**
* 这个接口要做三件事
* 1、获取验证码。
* 2、将验证码存入session
* 3、将验证码信息通过短信或者图形验证码发送出去。
* (将spring-boot-security-study-03接口里的那三步进行里封装)
*
*/
void processor(ServletWebRequest request) throws Exception;
由于图片验证码和短信验证码的 生成和保存、发送等流程是固定的。所以这里写一个抽象类来实现ValidateCodeProcessor接口,来实现相似部分。
AbstractValidateCodeProcessorService抽象类
@Component
public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService {
private static final String SEPARATOR = "/code/";
/**
* 操作session的工具集
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 这是Spring的一个特性,就是在项目启动的时候会自动收集系统中 {@link ValidateCodeGeneratorService} 接口的实现类对象
*
* key为bean name
*/
@Autowired
private Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap;
@Override
public void processor(ServletWebRequest request) throws Exception {
//第一件事
C validateCode = generate(request);
//第二件事
save(request, validateCode);
//第三件事
send(request, validateCode);
}
/**
* 生成验证码
*
*/
private C generate(ServletWebRequest request) {
String type = getProcessorType(request);
//这里 image+CodeGenerator = imgCodeGenerator 对应的就是ImageCodeGeneratorServiceService
ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR));
return (C) validateCodeGenerator.generate(request);
}
/**
* 保存验证码到session中
*/
private void save(ServletWebRequest request, C validateCode) {
//这里也是封装了一下
sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode);
}
/**
* 发送验证码 (只有发送验证码是需要自己去实现的。)
*/
protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;
/**
* 获取请求URL中具体请求的验证码类型
*
*/
private String getProcessorType(ServletWebRequest request) {
// 获取URI分割后的第二个片段 (/code/image 通过/code/ 切割后就只剩下 image
return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR);
}
}
简单说明:
1、这里用到了Spring一个特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 可以把ValidateCodeGeneratorService所以的实现类都放
到这个map中,key为bean的名称。
2、抽象类中实现了 ValidateCodeProcessor接口的processor方法,它主要是完成了验证码的创建、保存和发送的功能。
3、generate 方法根据传入的不同泛型而生成了特定的验证码。
4、save 方法是将生成的验证码实例对象存入到session中,两种验证码的存储方式一致,只是有个key不一致,所以代码也是通用的。
5、send 方法一个抽象方法,分别由ImageCodeProcessorService和SmsCodeProcessorService来具体实现,也是根据泛型来判断具体调用哪一个具体的实现类的send方法。
3、验证码的生成接口
上面说过验证的生成应该也是通过实现类
ValidateCodeGeneratorService
public interface ValidateCodeGeneratorService {
/**
* 这个常量也是用来 type+CodeGeneratorService获取对于bean对象
*/
String CODE_GENERATOR = "CodeGeneratorService";
/**
* 生成验证码
* 具体是图片验证码 还是短信验证码就需要对应的实现类
*/
ValidateCode generate(ServletWebRequest request);
}
图形验证码具体实现类
mageCodeGeneratorServiceImpl
@Data
@Component("imageCodeGeneratorService")
public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {
private static final String IMAGE_WIDTH_NAME = "width";
private static final String IMAGE_HEIGHT_NAME = "height";
private static final Integer MAX_COLOR_VALUE = 255;
@Autowired
private ValidateCodeProperties validateCodeProperties;
@Override
public ImageCode generate(ServletWebRequest request) {
int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, validateCodeProperties.getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME,validateCodeProperties.getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
// 生成画布
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
// 生成数字验证码
StringBuilder sRand = new StringBuilder();
for (int i = 0; i < validateCodeProperties.getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand.append(rand);
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(image, sRand.toString(), validateCodeProperties.getImage().getExpireIn());
}
/**
* 生成随机背景条纹
*
* @param fc 前景色
* @param bc 背景色
* @return RGB颜色
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > MAX_COLOR_VALUE) {
fc = MAX_COLOR_VALUE;
}
if (bc > MAX_COLOR_VALUE) {
bc = MAX_COLOR_VALUE;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
短信验证码具体实现类
SmsCodeGeneratorServiceImpl
@Data
@Component("smsCodeGeneratorService")
public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {
@Autowired
private ValidateCodeProperties validateCodeProperties;
@Override
public SmsCode generate(ServletWebRequest request) {
//生成随机数
String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength());
return new SmsCode(code, validateCodeProperties.getSms().getExpireIn());
}
}
4、验证码的发送逻辑类
获取的实现类写好了,我们在写发送具体的发送实现类,发送类的实现类是实现AbstractValidateCodeProcessorService抽象类的。
图片发送实现类
ImageCodeProcessorServiceImpl
@Component("imageCodeProcessorService")
public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> {
private static final String FORMAT_NAME = "JPEG";
/**
* 发送图形验证码,将其写到相应中
*
* @param request ServletWebRequest实例对象
* @param imageCode 验证码
*/
@Override
protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream());
}
}
短信发送具体实现类。这里只是后台输出就好了,实际中只要接入对于的SDK就可以了。
SmsCodeProcessorServiceImpl
@Component("smsCodeProcessorService")
public class SmsCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<SmsCode> {
private static final String SMS_CODE_PARAM_NAME = "mobile";
@Override
protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception {
//这里有一个参数也是前端需要传来的 就是用户的手机号
String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME);
// 这里仅仅写个打印,具体逻辑一般都是调用第三方接口发送短信
System.out.println("向手机号为:" + mobile + "的用户发送验证码:" + smsCode.getCode());
}
整个大致就是这样,我们再来测试一下。
三、测试
1、ValidateCodeController接口
获取验证码接口
@RestController
@Slf4j
public class ValidateCodeController {
@Autowired
private Map<String, ValidateCodeProcessorService> validateCodeProcessorMap;
@RequestMapping("/code/{type}")
public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
if(!StringUtils.equalsAny(type, "image", "sms")){
log.info("type类型错误 type={}",type);
return;
};
//根据type获取具体的实现类
ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR));
validateCodeProcessorService.processor(new ServletWebRequest(request, response));
}
}
2、获得图形验证码

获取成功
3、获取短信验证码
获取短信验证码需要多传一个参数就是mobile 手机号码

因为这里发送短信没有接第三方SDK,而是直接在控制台输出

参考
1、Spring Security技术栈开发企业级认证与授权(JoJo)
这一套架构设计真的非常的好,代码可读性和可用性都非常高,以后如果要接第三个验证码只要实现发送和获取的接口来自定义实a现就好了。很受启发!
别人骂我胖,我会生气,因为我心里承认了我胖。别人说我矮,我就会觉得好笑,因为我心里知道我不可能矮。这就是我们为什么会对别人的攻击生气。
攻我盾者,乃我内心之矛(20)
SpringSceurity(4)---短信验证码功能实现的更多相关文章
- SpringSceurity(5)---短信验证码登陆功能
SpringSceurity(5)---短信验证码登陆功能 有关SpringSceurity系列之前有写文章 1.SpringSecurity(1)---认证+授权代码实现 2.SpringSecur ...
- jQuery实现倒计时重新发送短信验证码功能示例
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- Android Studio精彩案例(五)《JSMS短信验证码功能实现》
转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 很多应用刚打开的时候,让我们输入手机号,通过短信验证码来登录该应用.那么,这个场景是怎么实现的呢?其实是很多开放平台提供了短信验证功能 ...
- 利用WeX5给手机APP增加短信验证码功能
帖子来源:http://bbs.wex5.com/thread-70908-1-1.html 遇到一个手机APP项目客户要求注册到APP上的用户手机号必须是真实的通过X5平台整合短信发送平台接口完成了 ...
- Java调用WebService接口实现发送手机短信验证码功能,java 手机验证码,WebService接口调用
近来由于项目需要,需要用到手机短信验证码的功能,其中最主要的是用到了第三方提供的短信平台接口WebService客户端接口,下面我把我在项目中用到的记录一下,以便给大家提供个思路,由于本人的文采有限, ...
- 这是一个简单的前台短信验证码功能 ajax实现异步处理 (发送和校验)
<script type="text/javascript"> var InterValObj; //timer变量,控制时间 var count = 60; //间隔 ...
- 四:java调接口实现发送手机短信验证码功能
1.点击获取验证码之前的样式: 2.输入正确的手机号后点击获取验证码之后的样式: 3.如果手机号已经被注册的样式: 4.如果一个手机号一天发送超过3次就提示不能发送: 二:前台的注册页面的代码:reg ...
- java + maven 实现发送短信验证码功能
如何使用java + maven的项目环境发送短信验证码,本文使用的是榛子云短信 的接口. 1. 安装sdk 下载地址: http://smsow.zhenzikj.com/doc/sdk.html ...
- Java加腾讯云实现短信验证码功能
一.概要 现如今在日常工作和生活中短信验证码对于我们来说是非常熟悉的,比较常见的注册账号或者交易支付时候,手机会收到一个短信验证码,我们可以通过验证码来有效验证身份,避免一些信息被盗. 验证身份 目前 ...
随机推荐
- MFC带参数启动指令发送与接收
MFC带参数启动指令发送与接收 发送 使用ShellExecute函数打开文件或执行程序. 函数原型: HINSTANCE ShellExecute( _In_opt_ HWND hwnd,//父窗口 ...
- python小练习-列表、字典
1. 切片(Slice) L=['a','b','c','d'] L[0:2]表示从索引0开始取,直到索引2为止,但不包括索引2.即索引0,1,正好是2个元素 如果第一个索引是0,还可以省略: L[: ...
- JavaScript数组常见用法
最近做一个项目中做一个竞猜游戏界面,游戏规则和彩票是一样的.在实现“机选一注”,“机选五注”的时候遇到数组的一些操作,例如产生['01', '02' ... '35']这样的数组,随机抽取不重复的元素 ...
- java--GC overhead limit exceeded--暂时修改JVM内存
第一步:选择要运行的java文件->RunAs->Run Configurations 第二步:找到(x)=Arguments,在VM arguments中设置:-Xms最小运行内存 -X ...
- wordpress获取文章特色图像路径函数wp_get_attachment_image_src()
特色图像是wordpress主要的文章缩略图功能,几乎全部wordpress模板都使用或支持特色图像.今天介绍的wp_get_attachment_image_src()函数就是获取文章特色图像路径的 ...
- uni-app运行到浏览器跨域H5页面的跨域问题解决方案
官方文档对跨域的解决方案推荐: https://ask.dcloud.net.cn/article/35267 更方便的解决方案 项目根目录直接创建一个vue.config.js文件,并在里面配置代理 ...
- 如何下载xshell家庭版
xshell是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议,加上友好炫酷的操作界面,深受广大码农的喜爱,是后端开发程序猿操 ...
- CF1340B Nastya and Scoreboard(暴搜剪枝/dp)
Question 一个n个数码位的分数板,每一个数码位都是一个七段数码管,现在给出每个数码位的显示情况,问再点亮k段数码管的话能显示的最大的数是多少,如果不能构成一串数字,就输出-1 Solution ...
- ubuntu 下安装 mysql 启动报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
环境: ubuntu LTS 18.04.1 mysql Ver 14.14 Distrib 5.7.29, for Linux (x86_64) 初探 linux,按照如下安装完mysql sudo ...
- 画出决策边界线--plot_2d_separator.py源代码【来自python机器学习基础教程】
import numpy as np import matplotlib.pyplot as plt from .plot_helpers import cm2, cm3, discrete_scat ...