在上一篇博客代码基础上进行进一步开发:
1:ValidateCodeController(生成验证码的接口)
/**
* 短信验证码
* @param request
* @param response
* @throws IOException
* @throws ServletRequestBindingException
*/
@GetMapping("/code/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException{
ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));
sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, smsCode);
String mobile = ServletRequestUtils.getRequiredStringParameter(request, "mobile");
smsCodeSender.send(mobile, smsCode.getCode());
}
2:这时需要对短信验证码和图片验证码进行改造
public class ValidateCode {
/**
* 随机数
*/
private String code;
/**
* 过期时间
*/
private LocalDateTime expireTime;
public ValidateCode(String code, int expireIn){
this.code = code;
//多少秒后
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
}
public ValidateCode(String code, LocalDateTime expireTime){
this.code = code;
this.expireTime = expireTime;
}
public boolean isExpried() {
return LocalDateTime.now().isAfter(expireTime);
}
get
set
}
3: ImageCode extends ValidateCode
public class ImageCode extends ValidateCode{
/**
* 图片
*/
private BufferedImage image;
public ImageCode(BufferedImage image, String code, int expireIn){
super(code,expireIn);
this.image = image;
}
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime){
super(code,expireTime);
this.image = image;
}
get
set
}
4:改造生成验证码的接口:
/**
* 定义验证码生成逻辑接口
* ValidateCodeGenerator
* @author zhailiang
*
*/
public interface ValidateCodeGenerator {
ValidateCode generate(ServletWebRequest request);
}
5:创建短信验证码的生成器:
/**
* SmsCodeGenerator内置验证码生成逻辑
* @author zhailiang
*
*/
@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
@Autowired
private SecurityProperties securityProperties;
@Override
public ValidateCode generate(ServletWebRequest request) {
String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLength());
return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn());
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
这里将短信验证码的生成方式用Component进行装配。而图片验证码使用一个Bean的配置类进行装配的,如下所示:
/**
* ValidateCodeBeanConfig配置如果用户自定义了自己的验证码的生成逻辑,内置的配置就不生效
* @author zhailiang
*
*/
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
* 项目可以自己实现验证码的具体实现,但是名字必须是imageCodeGenerator。
* 如果项目中没有实现,name就使用安全模块中自己实现的验证码逻辑
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator() {
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
/**
* 如果项目中配置SmsCodeSender接口的实现,就不会使用下面配置的实现方式
* @return
*/
@Bean
@ConditionalOnMissingBean(SmsCodeSender.class)
public SmsCodeSender smsCodeSender() {
return new DefaultSmsCodeSender();
}
}
同样这里发送短信验证码的第三方有很多,因此在这里进做了一个简单的在控制台输出的实现,可以根据项目自己实现短信验证的实现,如果自己项目中配置了自己额实现,项目默认的实现就不会生效。
6:短信发送的接口:
/**
* 短信发送的接口
* @author HZK
* @date 2018年7月28日 下午5:55:31
*/
public interface SmsCodeSender {
void send(String mobile, String code);
}
7:短信发送的默认实现:
/**
* @author zhailiang
* 默认的短信验证码的实现,可根据不同的业务具体实现
*/
public class DefaultSmsCodeSender implements SmsCodeSender {
@Override
public void send(String mobile, String code) {
System.out.println("向手机"+mobile+"发送短信验证码"+code);
}
}
8:接下来是一些配置:
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
private SmsCodeProperties sms = new SmsCodeProperties();
public ImageCodeProperties getImage() {
return image;
}
public void setImage(ImageCodeProperties image) {
this.image = image;
}
public SmsCodeProperties getSms() {
return sms;
}
public void setSms(SmsCodeProperties sms) {
this.sms = sms;
}
}
public class SmsCodeProperties {
private int length = 6;
private int expireIn = 60;
private String url;
public int getLength() {
return length;
}
public void setLength(int lenght) {
this.length = lenght;
}
public int getExpireIn() {
return expireIn;
}
public void setExpireIn(int expireIn) {
this.expireIn = expireIn;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
public class ImageCodeProperties extends SmsCodeProperties {
public ImageCodeProperties() {
setLength(4);
}
private int width = 67;
private int height = 23;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
9:对上述重复代码的重构:
ValidateCodeProcessor :
/**
* 校验码处理器,封装不同校验码的处理逻辑
*
* @author zhailiang
*
*/
public interface ValidateCodeProcessor {
/**
* 验证码放入session时的前缀
*/
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
/**
* 创建校验码
* ServletWebRequest封装请求和相应
* @param request
* @throws Exception
*/
void create(ServletWebRequest request) throws Exception;
}
抽象的实现:
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
@Override
public void create(ServletWebRequest request) throws Exception {
C validateCode = generate(request);
save(request, validateCode);
send(request, validateCode);
}
/**
* 生成校验码
* @param request
* @return
*/
@SuppressWarnings("unchecked")
private C generate(ServletWebRequest request) {
String type = getProcessorType(request);
ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(type + "CodeGenerator");
return (C) validateCodeGenerator.generate(request);
}
/**
* 保存校验码
* @param request
* @param validateCode
*/
private void save(ServletWebRequest request, C validateCode) {
sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX + getProcessorType(request).toUpperCase(),
validateCode);
}
/**
* 发送校验码,由子类实现
* @param request
* @param validateCode
* @throws Exception
*/
protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;
/**
* 根据请求的url获取校验码的类型
* @param request
* @return
*/
private String getProcessorType(ServletWebRequest request) {
return StringUtils.substringAfter(request.getRequest().getRequestURI(), "/code/");
}
}
其中send方法让具体的实现类实现:这里如果是图片验证码name就是写入流中,如果是短信验证码就发送出去:
/**
* 图片验证码处理器
*
* @author zhailiang
*
*/
@Component("imageCodeProcessor")
public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImageCode> {
/**
* 发送图形验证码,将其写到响应中
*/
@Override
protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
ImageIO.write(imageCode.getImage(), "JPEG", request.getResponse().getOutputStream());
}
}
@Component("smsCodeProcessor")
public class SmsCodeProcessor extends AbstractValidateCodeProcessor<ValidateCode> {
/**
* 短信验证码发送器
*/
@Autowired
private SmsCodeSender smsCodeSender;
@Override
protected void send(ServletWebRequest request, ValidateCode validateCode) throws Exception {
String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), "mobile");
smsCodeSender.send(mobile, validateCode.getCode());
}
}
页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>图形验证码:</td>
<td>
<input type="text" name="imageCode">
<img src="/code/image?width=200">
</td>
</tr>
<tr>
<td colspan='2'><input name="remember-me" type="checkbox" value="true" />记住我</td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
<h3>短信登录</h3>
<form action="/authentication/mobile" method="post">
<table>
<tr>
<td>手机号:</td>
<td><input type="text" name="mobile" value="13012345678"></td>
</tr>
<tr>
<td>短信验证码:</td>
<td>
<input type="text" name="smsCode">
<a href="/code/sms?mobile=13012345678">发送验证码</a>
</td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>