Spring Seurity系列(八)短信验证码接口开发

在上一篇博客代码基础上进行进一步开发:

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>

至此短信接口实现完成。

猜你喜欢

转载自blog.csdn.net/newhanzhe/article/details/81263466