SpringSceurity实现短信验证码功能的示例代码

一、思考

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现就好了。很受启发!

(0)

相关推荐

  • SpringSceurity实现短信验证码功能的示例代码

    一.思考 1.设计思路 在获取短信验证码功能和图形验证码还是有很多相似的地方,所以这里在设计获取短信验证的时候,将之前开发好的的图形验证码进一步整合.抽象与重构. 在获取验证码的时候,它们最大的不同在于: 图形验证码是通过接口返回获取給前端.而短信验证码而言是通过第三方API向我们手机推送. 但是它们在登陆的时候就有很大的不同了,对于图形验证码而言验证通过之前就走UsernamePasswordAuthenticationFilter 过滤器了开始校验用户名密码了. 但对于短信登陆而言,确实也需

  • php利用云片网实现短信验证码功能的示例代码

    本文将以php举例,介绍网页短信验证码功能的实现. 在众多的第三方短信服务商中我选择了云片网这个短信服务商,本文也将尽可能利用最简单的方式去帮助广大开发者解决短信验证码功能模块的实现. 再次之前我也参考了大部分网上的博客等,大多数都是把云片网的demo原封不动搬上去,对于我这个前端人员来说,根本毫无头绪,故此我将细致的讲解如何操作,以及献上我的源码. 我的业务流程就是通过点击发送验证码这个按钮,触发一个ajax请求事件,将手机号发送到后台,后台生成验证码发送到手机端,并返回这个验证码给前台进行验

  • Redis实现短信验证码登录的示例代码

    目录 效果图 pom.xml applicatoin.yml Redis配置类 controller serviceImpl mapper 效果图 发送验证码 输入手机号.密码以及验证码完成登录操作 pom.xml 核心依赖 <dependencies> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version&g

  • vue实现通过手机号发送短信验证码登录的示例代码

    本文主要介绍了vue实现通过手机号发送短信验证码登录的示例代码,分享给大家,具体如下: <template> <div class="get-mobile" @touchmove.prevent> <div class="main"> <div class="pt-20 pr-15 pl-15 pb-20"> <input class="input mb-15" v-mod

  • SpringSceurity实现短信验证码登陆

    一.短信登录验证机制原理分析 了解短信验证码的登陆机制之前,我们首先是要了解用户账号密码登陆的机制是如何的,我们来简要分析一下Spring Security是如何验证基于用户名和密码登录方式的, 分析完毕之后,再一起思考如何将短信登录验证方式集成到Spring Security中. 1.账号密码登陆的流程 一般账号密码登陆都有附带 图形验证码 和 记住我功能 ,那么它的大致流程是这样的. 1. 用户在输入用户名,账号.图片验证码后点击登陆.那么对于springSceurity首先会进入短信验证码

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

    本文实例讲述了jQuery实现倒计时重新发送短信验证码功能的方法.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html> <head> <script src="http://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script> <script type="text/javascript"> var countdown

  • 基于Redis实现短信验证码登录项目示例(附源码)

    目录 Redis短信登录流程描述 短信验证码的发送 短信验证码的验证 是否登录的验证 源码分析 模拟发送短信验证码 短信验证码的验证 校验是否登录 登录验证优化 Redis短信登录流程描述 短信验证码的发送 用户提交手机号,系统验证手机号是否有效,毕竟无效手机号会消耗你的短信验证次数还会导致系统的性能下降.如果手机号为无效的话就让用户重新提交手机号,如果有效就生成验证码并将该验证码作为value保存到redis中对应的key是手机号,之所以这么做的原因是保证key的唯一性,如果使用固定字符串作为

  • Spring MVC 中 短信验证码功能的实现方法

    在外部网站中短信的验证很有必要,比如在实现注册.验证用户信息等的情况下.在SpringMVC中的实现如下: 短信接口 短信接口,有些企业会购买的有移动的短信平台接口.如果是个人或者是小企业可以使用一些云服务的.比如百度的API Store上面的. 我使用的是:http://apistore.baidu.com/apiworks/servicedetail/1018.html 当然短信接口肯定都是要付费的,而且是基于模板的,具体的使用说明可以看这个网址里面的使用说明. 前端界面 前端的界面,可能如

  • Vue.js实现移动端短信验证码功能

    现在的短信验证码一般为4位或6位,则页面中会相应的显示4个或6个文本框,如图所示 当点击注册进入到输入验证码页面的时候,第一个框自动获取光标,依次输入,不可直接输入第三个或第四个框的值, input输入框是循环出来的,代码如下: <div class="sms_input"> <div v-for="n in sms.numbers-1"><input v-if="sms.show[n-1]" disabled=&q

  • Android中用Bmob实现短信验证码功能的方法详解

    这篇文章主要介绍发送验证码和校验验证码的功能,用到一个第三方平台Bmob,那Bmob是什么呢?Bmob可以开发一个云存储的移动应用软件,他提供了大量的标准的API接口,根据需要接入相关服务,开发者可以更加专注于应用的开发,让产品交付更快速,验证码功能就是其中一个. 一.跟其他第三方一样,我们开发之前要做一些准备工作. 1.首先,去官网注册一个帐号:http://www.bmob.cn/: 2.然后就可以创建应用了:具体怎么做Bmob说得很清楚了(官方操作介绍),如果你不想看,我简单说一下:点击右

随机推荐