Spring Security实现短信验证码登录

概述
手机验证码登录是目前很常见的一种登录方式,本文阐述基于 快速实现手机验证码登录 。本文建立在你对 基本原理有所了解的基础上,有兴趣的同学戳这里是关于 基本原理的文章:
本文实验环境:
思路
实现步骤
加入依赖
org.springframework.bootspring-boot-starter-securitycommons-langcommons-lang
创建手机验证码实体
第一步先创建一个手机验证码实体类,包含验一个证码字段和一个过期时间字段,还提供了两种构造器,提供两种不同设置过期时间的方式
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);}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public LocalDateTime getExpireTime() {return expireTime;}public void setExpireTime(LocalDateTime expireTime) {this.expireTime = expireTime;}}
创建验证码生成器
封装一个验证码生成器,方法生成一个四位数验证码
@Componentpublic class SmsCodeGenerator{public ValidateCode generate(ServletWebRequest request) {String code = RandomStringUtils.randomNumeric(4);return new ValidateCode(code, 60);}}
创建验证码发送器
封装一个验证码发送器,在这只是模拟发送验证码,并没有真正的向手机发送验证码,验证码在控制台打印出来,具体的实现向手机发送验证码需自己实现
@Componentpublic class DefaultSmsCodeSender{public void send(String mobile, String code) {System.out.println("向手机"+mobile+"发送短信验证码"+code);}}
生成验证码接口
创建一个接口用于生成验证,此接口会向外暴漏出来,不被 过滤器拦截,下文会写详细的 配置 。接口内生成验证码,并且作为key-value键值对保存在中,最后向手机号发送验证码
@RestControllerpublic class ValidateCodeController {@Autowiredprivate SmsCodeGenerator smsCodeGenerator;@Autowiredprivate DefaultSmsCodeSender defaultSmsCodeSender;@GetMapping("/code/sms")public void createSmsCode(HttpServletRequest request, HttpServletResponse response, HttpSession session, @RequestParam String mobile) throws IOException {ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));session.setAttribute(mobile, smsCode);defaultSmsCodeSender.send(mobile,smsCode.getCode());}}
增加手机验证码过滤器
自定义一个手机验证码过滤器,拦截请求URL是//(手机验证码登录接口)和请求方式是POST的请求,做验证码校验 。校验包括验证码是否过期等

Spring Security实现短信验证码登录

文章插图
public class ValidateCodeFilter extends OncePerRequestFilter {private AuthenticationFailureHandler authenticationFailureHandler;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain)throws ServletException, IOException {if(StringUtils.equals("/authentication/mobile", httpServletRequest.getRequestURI())&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")){try {validateSmsCode(httpServletRequest,httpServletRequest.getSession());}catch (ValidateCodeException e) {authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);return;}}filterChain.doFilter(httpServletRequest,httpServletResponse);}//校验手机验证码private void validateSmsCode(HttpServletRequest request, HttpSession session) throws ServletRequestBindingException {//请求里的手机号和验证码String mobileInRequest = request.getParameter("mobile");String codeInRequest = request.getParameter("smsCode");ValidateCode codeInSession = (ValidateCode) session.getAttribute(mobileInRequest);if (StringUtils.isBlank(codeInRequest)) {throw new ValidateCodeException("验证码的值不能为空");}if(codeInSession == null){throw new ValidateCodeException("该手机号未发送验证码");}if(codeInSession.isExpried()){session.removeAttribute(mobileInRequest);throw new ValidateCodeException("验证码已过期");}if(!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {throw new ValidateCodeException("验证码不匹配");}session.removeAttribute(mobileInRequest);}public AuthenticationFailureHandler getAuthenticationFailureHandler() {return authenticationFailureHandler;}public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {this.authenticationFailureHandler = authenticationFailureHandler;}}