短信验证码接口发送
实现短信验证码登录
- 开发短信验证码接口
- 如何校验短信验证码
- 重构代码
这里的套路与之前图形验证码的套路类似
开发短信验证码接口
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Autowired
private SecurityProperties securityProperties;
@Autowired
private ValidateCodeGenerate imageCodeGenerate;
@Autowired
private ValidateCodeGenerate smsCodeGenerate;
@Autowired
private SmsCodeSender smsCodeSender;
@GetMapping("/code/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException {
ValidateCode validateCode = smsCodeGenerate.generate(request);
String mobile = ServletRequestUtils.getRequiredStringParameter(request, "mobile");
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, validateCode.getCode());
smsCodeSender.send(mobile, validateCode.getCode());
}
配置类
@Configuration
public class ValidateCodeConfig {
@Autowired
private SecurityProperties securityProperties;
// spring 容器中如果存在imageCodeGenerate的bean就不会再初始化该bean了
// 条件注解
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerate")
public ValidateCodeGenerate imageCodeGenerate() {
ImageCodeGenerate imageCodeGenerate = new ImageCodeGenerate(securityProperties.getCode().getImage());
return imageCodeGenerate;
}
// 这里由于产生了多个ValidateCodeGenerate的实现类
// 所以需要使用name来区分
// 在注入的时候也需要用其他手段与该name相同的id注入才可以
// 当然还有其他的方式。可能可以使用:不同的子接口来分离短信和图形接口
// 比如 @Qualifier("imageCodeGenerate") 或则什么的参数名和这个相同
@Bean
@ConditionalOnMissingBean(name = "smsCodeGenerate")
public ValidateCodeGenerate smsCodeGenerate() {
SmsCodeGenerate smsCodeGenerate = new SmsCodeGenerate(securityProperties.getCode().getSms());
return smsCodeGenerate;
}
@Bean
@ConditionalOnMissingBean(DefaultSmsCodeSender.class)
public SmsCodeSender defaultSmsCodeSender() {
return new DefaultSmsCodeSender();
}
}
短信验证码生成类
package cn.mrcode.imooc.springsecurity.securitycore.validate.code.sms;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SmsCodeProperties;
import cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCode;
import cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCodeGenerate;
import org.apache.commons.lang3.RandomStringUtils;
import javax.servlet.http.HttpServletRequest;
public class SmsCodeGenerate implements ValidateCodeGenerate {
private SmsCodeProperties smsCodeProperties;
public SmsCodeGenerate(SmsCodeProperties smsCodeProperties) {
this.smsCodeProperties = smsCodeProperties;
}
@Override
public ValidateCode generate(HttpServletRequest request) {
int count = smsCodeProperties.getLength();
int expireIn = smsCodeProperties.getExpireIn();
String smsCode = RandomStringUtils.randomNumeric(count);
return new ValidateCode(smsCode, expireIn);
}
这里目前没有什么特别的,都是伪代码,提供一种思路。
还有就是贴出来的代码与之前图形验证码的部分代码重合了,就重构了;
由于发现有好多逻辑也是重复的。进行深度重构抽象。验证码处理器结构如下:
重构验证码逻辑
虚线是调用
- ValidateCodeController # 服务入口
- ValidateCodeProcessor # 验证码处理接口
- AbstractValidateCodeProcessor # 验证码处理 公共功能,使用模版方法模式聚合公共逻辑
- ImageCodeProcessor # 图片验证码处理:响应
- SmsCodeProcessor # 短信验证码处理:发送
- ValidateCodeGenerator # 生成接口
- ImageCodeGenerate # 图形验证码生成
- SmsCodeGenerator # 短信验证码生成
上图逻辑清晰,看着么多类,实际上是把变化的部分抽象成接口了,公共的逻辑使用模版方法模式封装起来了;
以后可以应对不同的变化,比如:
- 图形验证码或则短信验证码的 生成逻辑变了,提供ValidateCodeGenerator实现类即可
- 图形或则短信验证码的响应/发送逻辑变了,提供AbstractValidateCodeProcessor的子类实现
abstract void send
发送方法
经过上面的思路分析,就是把变化的流程单独拿出来了。
这里涉及到一个spring中的开发技巧:依赖查找
/**
* <pre>
* 收集系统中所有 {@link ValidateCodeGenerate} 接口的实现
* spring开发技巧-依赖查找:
* spring会查找所有ValidateCodeGenerate的实现
* beanName做为key,实现作为value注入这里
* </pre>
*/
@Autowired
private Map<String, ValidateCodeGenerate> validateCodeGenerates;
在 AbstractValidateCodeProcessor 中声明的该参数;再来看下在其他地方是怎么初始化的
@Configuration
public class ValidateCodeConfig {
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerate")
public ValidateCodeGenerate imageCodeGenerate() {
ImageCodeGenerate imageCodeGenerate = new ImageCodeGenerate(securityProperties.getCode().getImage());
return imageCodeGenerate;
}
@Bean
@ConditionalOnMissingBean(name = "smsCodeGenerate")
public ValidateCodeGenerate smsCodeGenerate() {
SmsCodeGenerate smsCodeGenerate = new SmsCodeGenerate(securityProperties.getCode().getSms());
return smsCodeGenerate;
}
}
上面使用了 条件排除,可以看出来这里有一个限制,就是短信和图形验证码的生成接口都使用的同一个;
那么这里在排除的时候就只能写上beanName来限制了;
对于上面的依赖查找技巧不会产生任何问题,但是对于使用处想替换该实现的时候。
对于bean的name只能是覆盖这里的同名name。否则就会出现配置不成功的问题
注意1:这里重构之后,会对 之前过滤器中的一部分代码有问题。
比如过滤器中图片验证码验证的地方SESSION_KEY的获取,目前处理是写死key的名称注意2:这里只是针对发送接口做调整;不是验证功能;
仔细体会,这个接口的开发思路和使用的技巧超越自己好多年了