SpringSceurity(4)---短信验证码功能实现

有关SpringSceurity系列之前有写文章

1、SpringSecurity(1)---认证+授权代码实现

2、SpringSecurity(2)---记住我功能实现

3、SpringSceurity(3)---图形验证码功能实现

一、思考

1、设计思路

在获取短信验证码功能和图形验证码还是有很多相似的地方,所以这里在设计获取短信验证的时候,将之前开发好的的图形验证码进一步整合、抽象与重构。

在获取验证码的时候,它们最大的不同在于: 图形验证码是通过接口返回获取給前端。而短信验证码而言是通过第三方API向我们手机推送

但是它们在登陆的时候就有很大的不同了,对于图形验证码而言验证通过之前就走 UsernamePasswordAuthenticationFilter 过滤器了开始校验用户名密码了。

但对于短信登陆而言,确实也需要先现在短信验证码是否通过,但是一旦通过他是不走 UsernamePasswordAuthenticationFilter,而是通过其它方式查询用户信息来校验

认证已经通过了。

这篇博客只写获取获取短信验证码的功能,不写通过短信验证码登陆的逻辑。

2、重构设计

这里才是最重要的,如何去设计和整合短信验证码和图形验证码的代码,是我们最应该思考的。如何将相似部分抽离出来,然后去实现不相同的部分。

整理后发现不同点主要在于

 1、获取验证码。因为对于图形验证码需要有个画布,而短信验证码并不需要,所以它们可以实现同一个接口,来完成不同的逻辑。
2、发送验证码。对于图形验证码来讲只要把验证码返给前端就可以,而短信验证码而言是通过第三方API将验证码发到我们的手机上。
所以这里也可以通过实现统一接口来具体实现不同的方法。

相同部分我可以通过抽象类来完成实现,不同部分可以通过具体的实现类来实现。

AbstractValidateCodeProcessorService 抽象类是用来实现两种验证码可以抽离的部分。ImageCodeProcessorServiceImpl

SmsCodeProcessorServiceImpl方法是来实现两种验证码不同的发送方式。

在简单看下时序图可能会更加明白点。

一个接口只有一个方法(processor)就是处理验证码,它其实需要做三件事。

 1、获取验证码。2、将验证码存入session。3、将验证码信息通过短信或者图形验证码发送出去。

首先讲生成获取验证码,这里有一个公共接口和两个实现类

对于保存验证码信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象类来完成,都不需要去实现。

对发送验证码信息而言,只需要实现AbstractValidateCodeProcessorService抽象类的send发送验证码接口即可。

整个大致接口设计就是这样,具体的可以通过代码来展示。

二、代码实现

1、验证码属性

短信验证码和图形验证后包含属性有 codeexpireTime,短信验证码只有这两个属性,而图形验证码还多一个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)---短信验证码功能实现的更多相关文章

  1. SpringSceurity(5)---短信验证码登陆功能

    SpringSceurity(5)---短信验证码登陆功能 有关SpringSceurity系列之前有写文章 1.SpringSecurity(1)---认证+授权代码实现 2.SpringSecur ...

  2. jQuery实现倒计时重新发送短信验证码功能示例

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  3. Android Studio精彩案例(五)《JSMS短信验证码功能实现》

    转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 很多应用刚打开的时候,让我们输入手机号,通过短信验证码来登录该应用.那么,这个场景是怎么实现的呢?其实是很多开放平台提供了短信验证功能 ...

  4. 利用WeX5给手机APP增加短信验证码功能

    帖子来源:http://bbs.wex5.com/thread-70908-1-1.html 遇到一个手机APP项目客户要求注册到APP上的用户手机号必须是真实的通过X5平台整合短信发送平台接口完成了 ...

  5. Java调用WebService接口实现发送手机短信验证码功能,java 手机验证码,WebService接口调用

    近来由于项目需要,需要用到手机短信验证码的功能,其中最主要的是用到了第三方提供的短信平台接口WebService客户端接口,下面我把我在项目中用到的记录一下,以便给大家提供个思路,由于本人的文采有限, ...

  6. 这是一个简单的前台短信验证码功能 ajax实现异步处理 (发送和校验)

    <script type="text/javascript"> var InterValObj; //timer变量,控制时间 var count = 60; //间隔 ...

  7. 四:java调接口实现发送手机短信验证码功能

    1.点击获取验证码之前的样式: 2.输入正确的手机号后点击获取验证码之后的样式: 3.如果手机号已经被注册的样式: 4.如果一个手机号一天发送超过3次就提示不能发送: 二:前台的注册页面的代码:reg ...

  8. java + maven 实现发送短信验证码功能

    如何使用java + maven的项目环境发送短信验证码,本文使用的是榛子云短信 的接口. 1. 安装sdk 下载地址: http://smsow.zhenzikj.com/doc/sdk.html ...

  9. Java加腾讯云实现短信验证码功能

    一.概要 现如今在日常工作和生活中短信验证码对于我们来说是非常熟悉的,比较常见的注册账号或者交易支付时候,手机会收到一个短信验证码,我们可以通过验证码来有效验证身份,避免一些信息被盗. 验证身份 目前 ...

随机推荐

  1. css 箭头三角形

    1.向下的三角形 .down{ display:inline-block; width:0px; height:0px; border-top:8px solid rgba(0, 0, 0, 0.65 ...

  2. React:Composition

    在日常的UI构建中,经常会遇到一种情况:组件本身更多是作为一个容器,它所包含的内容可能是动态的.未预先定义的.这时候它的内容取决另一个组件或外部的输入.比如弹层. props.children: Re ...

  3. Spring + Struts + Hibernate 简单封装通用接口

    1.BaseDao public interface BaseDao<T> { /** * 获取符合条件的记录数 * @param filter * @param sortName * @ ...

  4. mybatis随记

    JDBC问题:1.数据库配置信息硬编码 2.频繁创建,释放数据库连接 3.sql,设置参数,获取结果集硬编码,不通用   解决方案:1.配置文件 2.采用连接池 3.使用反射和内省   自定义持久层框 ...

  5. mysql事务与锁机制详解

    一.事务 1.事务简介 (1)事务的场景 转账:一个账户减少,另一个账户增加.两个动作同时成功或者同时失败.就要开启事务. (2)事务定义 事务是数据库管理系统执行过程中的一个逻辑单元,由一个有限的数 ...

  6. PAT-1018 Public Bike Management(dijkstra + dfs)

    1018. Public Bike Management There is a public bike service in Hangzhou City which provides great co ...

  7. (一) Vue在创建的时候 入口文件 及相关的路由配置(及子路由配置)

    1. 首先明确一点  在书写之前尽量保持相关的文件知道含义 比如 components 啥的 知道是要放什么东西 在这里介绍一下   由于 vue 不是系统学习 所以很多的创建方式可能不一样  就是有 ...

  8. eatwhatApp开发实战(七)

    之前我们为app添加了读取本地数据的功能和删除的功能.本次我们来将listview上item项的触控修改为item项上单一控件的触控事件.用item项上的button来实现删除数据. 先上布局: &l ...

  9. pandas的loc与iloc

    1. loc是用标签(也就是行名和列名)来查找,标签默认是数字,但也可以通过index参数指定为字符型等其他的类型. 格式是df.loc[行名,列名],如果列标签没有给出,则默认为查找指定行标签的所有 ...

  10. 图解MySQL索引(二)—为什么使用B+Tree

    失踪人口回归,近期换工作一波三折,耽误了不少时间,从今开始每周更新~ 索引是一种支持快速查询的数据结构,同时索引优化也是后端工程师的必会知识点.各个公司都有所谓的MySQL"军规" ...