SpringSecurity-短信验证码接口开发
??點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá) 今日推薦:一個(gè)線程池 bug 引發(fā)的 GC 思考!個(gè)人原創(chuàng)+1博客:點(diǎn)擊前往,查看更多 鏈接:https://segmentfault.com/a/1190000022040383前言
有時(shí)候我們需要有特殊登錄形式,比如說(shuō)短信驗(yàn)證碼登錄。他與驗(yàn)證碼登錄邏輯是不一樣的,所以不能使用Spring Security默認(rèn)提供的那套邏輯;需要自個(gè)去寫一個(gè)自定義身份認(rèn)證邏輯。實(shí)現(xiàn)步驟如下:
開(kāi)發(fā)短信驗(yàn)證碼接口
校驗(yàn)短信驗(yàn)證碼并登錄
重構(gòu)代碼
內(nèi)容
1.開(kāi)發(fā)短信驗(yàn)證碼接口
ValidateCodeController 我們之前已經(jīng)寫了圖形驗(yàn)證碼了,現(xiàn)在我們?cè)诖嘶A(chǔ)之上重構(gòu)代碼
1.1 創(chuàng)建驗(yàn)證碼實(shí)體
public class ValidateCode {private String code;/*** 過(guò)期時(shí)間*/private LocalDateTime expireTime;public ValidateCode(String code, int expireIn){this.code=code;/*** 過(guò)期時(shí)間傳遞的參數(shù)應(yīng)該是一個(gè)秒數(shù):根據(jù)這個(gè)秒數(shù)去計(jì)算過(guò)期時(shí)間*/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;} }圖片驗(yàn)證碼繼承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改造
因?yàn)镮mageCode繼承ValidateCode,所以我們這個(gè)接口返回父類,繼承、面向接口編程。
public interface ValidateCodeGenerator {/*** 生成驗(yàn)證碼* @param request* @return*/ValidateCode generate(ServletWebRequest request); }1.3 短信發(fā)送封裝
1.定義短信發(fā)送接口
public interface SmsCodeSender {/*** 給某個(gè)手機(jī)發(fā)送短信驗(yàn)證碼* @param mobile* @param code*/void send(String mobile,String code); }2.定義短信接口默認(rèn)實(shí)現(xiàn)類 模擬定義默認(rèn)接口發(fā)送實(shí)現(xiàn)類
public class DefaultSmsCodeSender implements SmsCodeSender {@Overridepublic void send(String mobile, String code) {System.out.println("向手機(jī):"+mobile+" 發(fā)送短信驗(yàn)證碼:"+code);} }3.ValidateCodeBeanConfig里面注入
@Configuration public class ValidateCodeBeanConfig {@Autowiredprivate SecurityProperties securityProperties;/** 這個(gè)配置與我們?cè)贗mageCodeGenerator上面加一個(gè)注解是類似的,但是這樣配置靈活,* 可以添加注解:@ConditionalOnMissingBean 作用是:在初始化這個(gè)bean的時(shí)候,* 先到spring容器去查找imageCodeGenerator,如果有一個(gè)imageCodeGenerator時(shí)候,* 就不會(huì)再用下面代碼去創(chuàng)建**/@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短信驗(yàn)證碼生成
@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.根據(jù)隨機(jī)數(shù)生成圖片* 2.將隨機(jī)數(shù)存到session中* 3.將生成圖片寫到接口的響應(yīng)中*/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.根據(jù)隨機(jī)數(shù)生成圖片* 2.將隨機(jī)數(shù)存到session中* 3.調(diào)用短信服務(wù):將短信發(fā)送到指定平臺(tái)*/ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,smsCode);//3.調(diào)用短信服務(wù):將短信發(fā)送到指定平臺(tái),我們封裝成如下接口:String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");smsCodeSender.send(mobile,smsCode.getCode());} }1.3 前端頁(yè)面
<h3>短信登錄</h3> <form action="/authentication/mobile" method="post"><table><tr><td>手機(jī)號(hào):</td><td><input type="text" name="mobile" value="13226595347"></td></tr><tr><td>短信驗(yàn)證碼</td><td><input type="text" name="smsCode"><a href="/code/sms?mobile=13012345678">發(fā)送驗(yàn)證碼</a></td></tr><tr><td colspan="2"><button type="submit">登錄</button></td></tr></table> </form>1.4 添加短信驗(yàn)證碼配置類
我們抽取短信驗(yàn)證碼如下屬性SmsCodeProperties:
public class SmsCodeProperties {private int length = 6;//長(zhǎng)度private int expireIn = 60;//過(guò)期時(shí)間private String url;//要處理的url//getter setter }并且圖片驗(yàn)證碼和其有很大重復(fù)部分,我們用繼承關(guān)系替代。但是圖片驗(yàn)證碼默認(rèn)是4位,而短信驗(yàn)證碼是6位,如何處理呢?我們?cè)诟割惸J(rèn):length = 6 但是在圖片驗(yàn)證碼構(gòu)造器中: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 }主要可配置的是長(zhǎng)度和過(guò)期時(shí)間 我們?cè)赩alidateCodeProperties加一個(gè)配置
1.5 添加短信驗(yàn)證碼生成器
短信驗(yàn)證碼生成器,我們使用@Component("smsCodeGenerator")注解注入到Spring
圖片驗(yàn)證碼生成器, @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 生成驗(yàn)證碼接口: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.根據(jù)隨機(jī)數(shù)生成圖片* 2.將隨機(jī)數(shù)存到session中* 3.將生成圖片寫到接口的響應(yīng)中*/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.根據(jù)隨機(jī)數(shù)生成短信驗(yàn)證碼* 2.將隨機(jī)數(shù)存到session中* 3.調(diào)用短信服務(wù):將短信發(fā)送到指定平臺(tái)*/ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,smsCode);//3.調(diào)用短信服務(wù):將短信發(fā)送到指定平臺(tái),我們封裝成如下接口:String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile");smsCodeSender.send(mobile,smsCode.getCode());} }我們觀察到:生成圖形驗(yàn)證碼和生成短信驗(yàn)證碼的邏輯是差不多的,都是3步: 1.生成驗(yàn)證碼 2.保存驗(yàn)證碼到session中 3.將驗(yàn)證碼發(fā)送出去(一個(gè)是發(fā)送到response頁(yè)面前端;一個(gè)是發(fā)送到客戶手機(jī)號(hào)上面)
像這種主干邏輯相同,其中個(gè)別步驟不一樣的,我們一般會(huì)使用“模板方法模式”將其抽象。
1.7 模板方法模式重構(gòu)ValidateCodeController中生成驗(yàn)證碼
1.7.1 重構(gòu)完的代碼邏輯如下:
image.png聲明一個(gè)ValidateCodeProcessor接口,這個(gè)接口有一個(gè)抽象的實(shí)現(xiàn): AbstractValidateCodeProcessor(之前短信/圖片驗(yàn)證碼的流程邏輯會(huì)寫到這里面)
具體的發(fā)送是不一樣的:一種是請(qǐng)求返回,一種是調(diào)用短信運(yùn)營(yíng)商返回。這些不同的地方,會(huì)讓其子類去實(shí)現(xiàn)。
注意: 1.ValidateCodeProcessor里面封裝了處理整個(gè)驗(yàn)證碼的生成流程的:包括:a.生成驗(yàn)證碼 b.存放session c.發(fā)送出去
2.具體的生成邏輯在:ValidateCodeGenerator:他只是封裝了:ValidateCodeProcessor接口的一部分。這也是我們?cè)O(shè)計(jì)思想中分層去封裝。當(dāng)業(yè)務(wù)發(fā)生變化時(shí)候,根據(jù)業(yè)務(wù)發(fā)生變化的力度去實(shí)現(xiàn)業(yè)務(wù)邏輯
1.7.2 AbstractValidateCodeProcessor下generate邏輯
/***使用依賴查找模式改造* 收集系統(tǒng)中所有的 {@link ValidateCodeGenerator} 接口的實(shí)現(xiàn)。*找到之后,把Spring中的bean的名字作為key *然后ValidateCodeGenerator作為value放到map里面去;*目前ValidateCodeGenerator的實(shí)現(xiàn)有兩個(gè):圖形驗(yàn)證碼實(shí)現(xiàn)和短信驗(yàn)證碼實(shí)現(xiàn)*/ @Autowired private Map<String, ValidateCodeGenerator> validateCodeGenerators;1.7.3 ValidateCodeController
@RestController public class ValidateCodeController {@Autowiredprivate Map<String, ValidateCodeProcessor> validateCodeProcessors;//將以上2個(gè)服務(wù)變成一個(gè)服務(wù)@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之前授權(quán)是針對(duì):"/code/image"現(xiàn)在變成:"/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();////因?yàn)槭荍dbc操作,所以我們需要注入數(shù)據(jù)源:org.springframework.jdbc.core.support.JdbcDaoSupport//tokenRepository繼承org.springframework.jdbc.core.support.JdbcDaoSupporttokenRepository.setDataSource(dataSource);System.out.println("PersistentTokenRepository--dataSource:>dataSource");//tokenRepository.setCreateTableOnStartup(true);//系統(tǒng)啟動(dòng)的時(shí)候創(chuàng)建:CREATE_TABLE_SQL表return tokenRepository;}/*** 定義web安全配置類:覆蓋config方法* 1.參數(shù)為HttpSecurity*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** 定義了任何請(qǐng)求都需要表單認(rèn)證*/ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);validateCodeFilter.setSecurityProperties(securityProperties);//傳遞securityPropertiesvalidateCodeFilter.afterPropertiesSet();http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)//自定義的額過(guò)濾器加到UsernamePasswordAuthenticationFilter前面去.formLogin()//表單登錄---指定了身份認(rèn)證方式// .loginPage("/login.html").loginPage("/authentication/require").loginProcessingUrl("/authentication/form")//配置UsernamePasswordAuthenticationFilter需要攔截的請(qǐng)求.successHandler(myAuthenticationSuccessHandler)//表單登錄成功之后用自帶的處理器.failureHandler(myAuthenticationFailureHandler)//表單登錄失敗之后用自帶的處理器// http.httpBasic()//http的basic登錄.and().rememberMe().tokenRepository(persistentTokenRepository())//配置remeberMe的token操作.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())//配置token失效秒數(shù).userDetailsService(userDetailsService)//配置操作數(shù)據(jù)庫(kù)用戶的service.and().authorizeRequests()//對(duì)請(qǐng)求進(jìn)行授權(quán).antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/*").permitAll()//對(duì)匹配login.html的請(qǐng)求允許訪問(wèn).anyRequest()//任何請(qǐng)求.authenticated().and().csrf().disable();//都需要認(rèn)證} }我們重啟服務(wù)試一下:
image.png總結(jié)
以上是生活随笔為你收集整理的SpringSecurity-短信验证码接口开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: reentrantLock 和 sync
- 下一篇: 牛人 20000 字的 Spring C