在 Spring Security 中基于表单的认证模式,默认就是密码帐号登录认证,那么对于短信验证码+登录的方式,Spring Security 没有现成的接口可以使用,所以需要自己的封装一个类似的认证过滤器和认证处理器实现短信认证。
短信验证码认证
验证码对象类设计
和图片验证码一样,需要自己封装一个验证码对象,用来生成手机验证码并发送给手机。因为图片验证码和手机验证码对象的区别就在于前者多了个图片对象,所以两者共同部分抽象出来可以设计成一个ValidateCode
类,这个类里面只存放验证码和过期时间,短信验证码直接使用这个类即可:
import java.time.LocalDateTime;
import lombok.Data;
@Data
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 boolean isExpried() {return LocalDateTime.now().isAfter(getExpireTime());}
public ValidateCode(String code, LocalDateTime expireTime) {
super();
this.code = code;
this.expireTime = expireTime;
}
}
图片验证码承继此类:
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
import org.woodwhales.king.validate.code.ValidateCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper=false)
public class ImageCode extends ValidateCode {private BufferedImage image;public ImageCode(BufferedImage image, String code, int expireId) {super(code, LocalDateTime.now().plusSeconds(expireId));this.image = image;}public ImageCode(BufferedImage image, String code, LocalDateTime localDateTime) {super(code, localDateTime);this.image = image;}
}
验证码生成类设计
由于图片和短信类均可以生成相应的验证码,所以直接设计一个验证码生成接口,具体实现类根据业务进行实现:
import org.springframework.web.context.request.ServletWebRequest;
public interface ValidateCodeGenerator {
ValidateCode generate(ServletWebRequest request);
}
这里的传参设计成了
ServletWebRequest
是能够根据前端请求中的参数进行不同的业务实现
目前实现累只有图片生成器和验证码生成器:
// 图片验证码生成器
@Component("imageCodeGenerator")
public class ImageCodeGenerator implements ValidateCodeGenerator {/** * 生成图形验证码 * @param request * @return */
@Overridepublic ValidateCode generate(ServletWebRequest request) {……return new ImageCode(image, sRand, SecurityConstants.EXPIRE_SECOND);}
}
// 短信验证码生成器
@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
@Override
public ValidateCode generate(ServletWebRequest request) {
String code = RandomStringUtils.randomNumeric(SecurityConstants.SMS_RANDOM_SIZE);return new ValidateCode(code, SecurityConstants.SMS_EXPIRE_SECOND);
}
}
短信验证码发送接口设计
短信验证码生成之后,需要设计接口依赖短信服务提供商进行验证码发送,因此至少设计一个统一的接口,供短信服务提供商生成发送短信服务:
public interface SmsCodeSender {
// 至少需要手机号和验证码
void send(String mobile, String code);
}
为了演示,设计一个虚拟的默认短信发送器,只在日志文件中打印一行log:
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
/**
* 短信发送模拟
* @author Administrator
*
*/
@Slf4j
@Service
public class DefaultSmsCodeSender implements SmsCodeSender {@Overridepublic void send(String mobile, String code) { log.debug("send to mobile :{}, code : {}", mobile, code);}
}
短信验证码请求Controller
所有验证码的请求都在统一的ValidateCodeController
里,这里注入了两个验证码生成器ValidateCodeGenerator
,后期可以利用 spring 的依赖查找/搜索技巧来重构代码,另外所有的请求也是可以做成动态配置,这里临时全部 hardCode 在代码里:
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.sp