点击上方 好好学java ,选择 星标 公众号

重磅资讯、干货,第一时间送达
今日推荐:一个线程池 bug 引发的 GC 思考!个人原创+1博客:点击前往,查看更多
链接:https://segmentfault.com/a/1190000022040383

前言

有时候我们需要有特殊登录形式,比如说短信验证码登录。他与验证码登录逻辑是不一样的,所以不能使用Spring Security默认提供的那套逻辑;需要自个去写一个自定义身份认证逻辑。实现步骤如下:

  1. 开发短信验证码接口

  2. 校验短信验证码并登录

  3. 重构代码

内容

1.开发短信验证码接口

ValidateCodeController 我们之前已经写了图形验证码了,现在我们在此基础之上重构代码

1.1 创建验证码实体

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(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;}
}

图片验证码继承ValidateCode

public class ImageCode extends ValidateCode{private BufferedImage image;public ImageCode(BufferedImage image,String code,int expireIn){super(code,expireIn);this.image=image;}public BufferedImage getImage() {return image;}public void setImage(BufferedImage image) {this.image = image;}
}

1.2 ValidateCodeGenerator改造

因为ImageCode继承ValidateCode,所以我们这个接口返回父类,继承、面向接口编程。

public interface ValidateCodeGenerator {/*** 生成验证码* @param request* @return*/ValidateCode generate(ServletWebRequest request);
}

1.3 短信发送封装

1.定义短信发送接口

public interface SmsCodeSender {/*** 给某个手机发送短信验证码* @param mobile* @param code*/void send(String mobile,String code);
}

2.定义短信接口默认实现类 模拟定义默认接口发送实现类

public class DefaultSmsCodeSender implements SmsCodeSender {@Overridepublic void send(String mobile, String code) {System.out.println("向手机:"+mobile+" 发送短信验证码:"+code);}
}

3.ValidateCodeBeanConfig里面注入

@Configuration
public class ValidateCodeBeanConfig {@Autowiredprivate SecurityProperties securityProperties;/** 这个配置与我们在ImageCodeGenerator上面加一个注解是类似的,但是这样配置灵活,* 可以添加注解:@ConditionalOnMissingBean 作用是:在初始化这个bean的时候,* 先到spring容器去查找imageCodeGenerator,如果有一个imageCodeGenerator时候,* 就不会再用下面代码去创建**/@Bean@ConditionalOnMissingBean(name="imageCodeGenerator")public ValidateCodeGenerator imageCodeGenerator(){//方法的名字就是放到Spring容器里bean的名字ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();imageCodeGenerator.setSecurityProperties(securityProperties);return imageCodeGenerator;}@Bean@ConditionalOnMissingBean(SmsCodeSender.class)public SmsCodeSender smsCodeSender(){//方法的名字就是放到Spring容器里bean的名字return new DefaultSmsCodeSender();}
}

1.2 ValidateCodeController短信验证码生成

@RestController
public class ValidateCodeController {public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();@Autowiredprivate ValidateCodeGenerator imageCodeGenerator;@Autowiredprivate ValidateCodeGenerator smsCodeGenerator;@Autowiredprivate SmsCodeSender smsCodeSender;@GetMapping("/code/image")public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {/*** 1.根据随机数生成图片* 2.将随机数存到session中* 3.将生成图片写到接口的响应中*/ImageCode imageCode = (ImageCode) imageCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());}@GetMapping("/code/sms")public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {/*** 1.根据随机数生成图片* 2.将随机数存到session中* 3.调用短信服务:将短信发送到指定平台*/ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,smsCode);//3.调用短信服务:将短信发送到指定平台,我们封装成如下接口:String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");smsCodeSender.send(mobile,smsCode.getCode());}
}

1.3 前端页面

<h3>短信登录</h3>
<form action="/authentication/mobile" method="post"><table><tr><td>手机号:</td><td><input type="text" name="mobile" value="13226595347"></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>

1.4 添加短信验证码配置类

我们抽取短信验证码如下属性SmsCodeProperties:

public class SmsCodeProperties {private int length = 6;//长度private int expireIn = 60;//过期时间private String url;//要处理的url//getter setter
}

并且图片验证码和其有很大重复部分,我们用继承关系替代。但是图片验证码默认是4位,而短信验证码是6位,如何处理呢?我们在父类默认:length = 6 但是在图片验证码构造器中:setLength(4);ImageCodeProperties:

public class ImageCodeProperties extends SmsCodeProperties{private int width = 67;private int height = 23;public ImageCodeProperties(){setLength(4);}
}

ValidateCodeProperties配置:

public class ValidateCodeProperties {private ImageCodeProperties image = new ImageCodeProperties();private SmsCodeProperties sms = new SmsCodeProperties();//getter setter
}

主要可配置的是长度和过期时间 我们在ValidateCodeProperties加一个配置

1.5 添加短信验证码生成器

短信验证码生成器,我们使用@Component("smsCodeGenerator")注解注入到Spring

图片验证码生成器, @Bean @ConditionalOnMissingBean(name="imageCodeGenerator")注解注入到Spring

@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {private SecurityProperties securityProperties;@Overridepublic 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;}
}

1.6 生成验证码接口:controller

@RestController
public class ValidateCodeController {public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();@Autowiredprivate ValidateCodeGenerator imageCodeGenerator;@Autowiredprivate ValidateCodeGenerator smsCodeGenerator;@Autowiredprivate SmsCodeSender smsCodeSender;@GetMapping("/code/image")public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {/*** 1.根据随机数生成图片* 2.将随机数存到session中* 3.将生成图片写到接口的响应中*/ImageCode imageCode = (ImageCode) imageCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());}@GetMapping("/code/sms")public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {/*** 1.根据随机数生成短信验证码* 2.将随机数存到session中* 3.调用短信服务:将短信发送到指定平台*/ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,smsCode);//3.调用短信服务:将短信发送到指定平台,我们封装成如下接口:String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");smsCodeSender.send(mobile,smsCode.getCode());}
}

我们观察到:生成图形验证码和生成短信验证码的逻辑是差不多的,都是3步: 1.生成验证码 2.保存验证码到session中 3.将验证码发送出去(一个是发送到response页面前端;一个是发送到客户手机号上面)

像这种主干逻辑相同,其中个别步骤不一样的,我们一般会使用“模板方法模式”将其抽象。

1.7 模板方法模式重构ValidateCodeController中生成验证码

1.7.1 重构完的代码逻辑如下:

image.png

声明一个ValidateCodeProcessor接口,这个接口有一个抽象的实现: AbstractValidateCodeProcessor(之前短信/图片验证码的流程逻辑会写到这里面)

具体的发送是不一样的:一种是请求返回,一种是调用短信运营商返回。这些不同的地方,会让其子类去实现。

注意: 1.ValidateCodeProcessor里面封装了处理整个验证码的生成流程的:包括:a.生成验证码 b.存放session c.发送出去

2.具体的生成逻辑在:ValidateCodeGenerator:他只是封装了:ValidateCodeProcessor接口的一部分。这也是我们设计思想中分层去封装。当业务发生变化时候,根据业务发生变化的力度去实现业务逻辑

1.7.2 AbstractValidateCodeProcessor下generate逻辑
/***使用依赖查找模式改造* 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。*找到之后,把Spring中的bean的名字作为key *然后ValidateCodeGenerator作为value放到map里面去;*目前ValidateCodeGenerator的实现有两个:图形验证码实现和短信验证码实现*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
1.7.3 ValidateCodeController
@RestController
public class ValidateCodeController {@Autowiredprivate Map<String, ValidateCodeProcessor> validateCodeProcessors;//将以上2个服务变成一个服务@GetMapping("/code/{type}")public void createCode(@PathVariable String type,HttpServletRequest request, HttpServletResponse response) throws Exception {validateCodeProcessors.get(type+"CodeProcessor").create(new ServletWebRequest(request,response));}
}
1.7.4 WebSecurityConfig

WebSecurityConfig之前授权是针对:"/code/image"现在变成:"/code/*"

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate SecurityProperties securityProperties;@Autowiredprivate MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@Autowiredprivate MyAuthenticationFailureHandler myAuthenticationFailureHandler;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate DataSource dataSource;@Beanpublic PasswordEncoder  passwordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();////因为是Jdbc操作,所以我们需要注入数据源:org.springframework.jdbc.core.support.JdbcDaoSupport//tokenRepository继承org.springframework.jdbc.core.support.JdbcDaoSupporttokenRepository.setDataSource(dataSource);System.out.println("PersistentTokenRepository--dataSource:>dataSource");//tokenRepository.setCreateTableOnStartup(true);//系统启动的时候创建:CREATE_TABLE_SQL表return tokenRepository;}/*** 定义web安全配置类:覆盖config方法* 1.参数为HttpSecurity*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** 定义了任何请求都需要表单认证*/ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);validateCodeFilter.setSecurityProperties(securityProperties);//传递securityPropertiesvalidateCodeFilter.afterPropertiesSet();http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//自定义的额过滤器加到UsernamePasswordAuthenticationFilter前面去.formLogin()//表单登录---指定了身份认证方式// .loginPage("/login.html").loginPage("/authentication/require").loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要拦截的请求.successHandler(myAuthenticationSuccessHandler)//表单登录成功之后用自带的处理器.failureHandler(myAuthenticationFailureHandler)//表单登录失败之后用自带的处理器// http.httpBasic()//http的basic登录.and().rememberMe().tokenRepository(persistentTokenRepository())//配置remeberMe的token操作.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())//配置token失效秒数.userDetailsService(userDetailsService)//配置操作数据库用户的service.and().authorizeRequests()//对请求进行授权.antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/*").permitAll()//对匹配login.html的请求允许访问.anyRequest()//任何请求.authenticated().and().csrf().disable();//都需要认证}
}

我们重启服务试一下:

image.png

SpringSecurity-短信验证码接口开发相关推荐

  1. 手机短信验证码接口在各领域的应用

    很多网站.APP应用.内部管理软件在使用过程中,都需要会员进行手机号码验证.登录验证.操作验证,在各种验证形式中,手机短信验证的方式最为简单和方便.根据不同的应用领域,手机短信验证可以广泛应用在如下方 ...

  2. 如何防止短信验证码接口、登录注册入口被恶意调用攻击?

    目录 前言 1.短信验证码是什么? 2.为什么要对短信验证码进行防护? 3.有哪些常见的防护手段? 4.这些防护手段有没有用呢,到底该如何选择? 5.结语 前言 最近遇到一个关于防止短信验证码被刷的问 ...

  3. 基于redis的短信验证码服务开发

    基于redis的短信验证码服务开发 目前可以提供的验证码服务平台有很多,这里选择阿里大于短信验证码服务平台,里面有10元体验卷可以免费试用,不多说上代码.写代码之前需要去阿里大于平台申请验证码服务,同 ...

  4. php短信接口加密_PHP短信接口、PHP短信验证码接口源码

    PHP短信接口.PHP短信验证码接口源码 时间:2016-06-13 11:53 来源:原创 作者:admin PHP短信接口文档源码,PHP发短信接口,PHP在线发短信,PHP微信发短信接口 /* ...

  5. 短信验证码接口的应用场景和优势

    短信验证码接口是常用于网络平台的一种短信功能接口,它在用户注册登录.密码找回.账户变更.确认支付.活动认证等场景都会应用到,短信接口作为网络平台与用户之间重要交互手段,凭借其速度快.到达率高.安全性好 ...

  6. 如何防止恶意攻击短信验证码接口

    如何防止恶意攻击短信验证码接口 1 自研技术 2 自研验证码被破解怎么办? 3 所有图形验证码都被破解怎么办? 1 自研技术 1.手机号码限制:限制单个手机号码每天的最大发送次数.超过次数不能发送短信 ...

  7. java防止注册刷短信攻击_java面试(1)如何防止恶意攻击短信验证码接口

    防止恶意攻击短信验证码接口方法 1.手机号码限制:限制单个手机号码每天的最大发送次数.超过次数不能发送短信,可以考虑将手机号码加入黑名单,禁止1天. 2.短信发送时间间隔限制:限制同一个手机号码重复发 ...

  8. 【转载】C语言,利用网络短信验证码接口实现手机短信发送

    利用网络短信验证码接口实现手机短信发送 (历史代码,贴出学习) resource.h //resource.h//{{NO_DEPENDENCIES}} // Microsoft Visual C++ ...

  9. Httpclient调用短信验证码接口

    一.简介 HttpClient是Apache Jakarta Common下的子项目,用来提供高效的.最新的.功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议.Ht ...

最新文章

  1. Powershell执行文件和脚本
  2. docker安装问题:E: Package 'docker-ce' has no installation candidate
  3. druid读取hdfs文件
  4. Apache下有效防止盗链仿下载的解决办法
  5. 【编译原理】语言的定义
  6. 10个小技巧助您写出高性能的ASP.NET Core代码
  7. JAVA判断素数法+引用方法
  8. oracle查看数据库文件大小
  9. Android 音视频开发 视频编码,音频编码格式
  10. python如何进行数据挖掘_如何使用python实现文本数据挖掘?
  11. 基于STM32的简易数码相册
  12. 一键批量下载皮皮虾视频
  13. 重力加速度传感器角度输出
  14. unity粒子系统碰撞
  15. CentOS 7 中 pptpd安装
  16. [POI2005] SZA-Template
  17. CodeGym一个学习平台
  18. 100万补贴!东湖高新区知识产权运营服务体系建设项目申报时间、条件流程大全
  19. HDU2549 壮志难酬【水题+输入输出】
  20. 全国心力衰竭日:重症心衰的黑科技——永久型人工心脏

热门文章

  1. MQTT数据处理之从tcp连接获取数据过程
  2. vim编辑器的常用技巧
  3. stdthread(6)并发lockGuard
  4. C++ Primer 5th笔记(chap 13 拷贝控制)实例1
  5. C++ Primer 5th笔记(5)chapter5 语句
  6. 【django】创建模型类
  7. Linux常用的基本命令ls、cd、mkdir(一)
  8. [ARM-assembly]-A64指令集合总结
  9. 用_beginthreadex不用 CreateThread
  10. (12)调用门阶段测试