您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了SpringSceurity如何實(shí)現(xiàn)短信驗(yàn)證碼功能,內(nèi)容簡(jiǎn)而易懂,希望大家可以學(xué)習(xí)一下,學(xué)習(xí)完之后肯定會(huì)有收獲的,下面讓小編帶大家一起來看看吧。
一、思考
1、設(shè)計(jì)思路
在獲取短信驗(yàn)證碼功能和圖形驗(yàn)證碼還是有很多相似的地方,所以這里在設(shè)計(jì)獲取短信驗(yàn)證的時(shí)候,將之前開發(fā)好的的圖形驗(yàn)證碼進(jìn)一步整合、抽象與重構(gòu)。
在獲取驗(yàn)證碼的時(shí)候,它們最大的不同在于: 圖形驗(yàn)證碼是通過接口返回獲取給前端。而短信驗(yàn)證碼而言是通過第三方API向我們手機(jī)推送。
但是它們?cè)诘顷懙臅r(shí)候就有很大的不同了,對(duì)于圖形驗(yàn)證碼而言驗(yàn)證通過之前就走UsernamePasswordAuthenticationFilter 過濾器了開始校驗(yàn)用戶名密碼了。
但對(duì)于短信登陸而言,確實(shí)也需要先現(xiàn)在短信驗(yàn)證碼是否通過,但是一旦通過他是不走 UsernamePasswordAuthenticationFilter,而是通過其它方式查詢用戶信息來校驗(yàn)
認(rèn)證已經(jīng)通過了。
這篇博客只寫獲取獲取短信驗(yàn)證碼的功能,不寫通過短信驗(yàn)證碼登陸的邏輯。
2、重構(gòu)設(shè)計(jì)
這里才是最重要的,如何去設(shè)計(jì)和整合短信驗(yàn)證碼和圖形驗(yàn)證碼的代碼,是我們最應(yīng)該思考的。如何將相似部分抽離出來,然后去實(shí)現(xiàn)不相同的部分。
整理后發(fā)現(xiàn)不同點(diǎn)主要在于
1、獲取驗(yàn)證碼。因?yàn)閷?duì)于圖形驗(yàn)證碼需要有個(gè)畫布,而短信驗(yàn)證碼并不需要,所以它們可以實(shí)現(xiàn)同一個(gè)接口,來完成不同的邏輯。
2、發(fā)送驗(yàn)證碼。對(duì)于圖形驗(yàn)證碼來講只要把驗(yàn)證碼返給前端就可以,而短信驗(yàn)證碼而言是通過第三方API將驗(yàn)證碼發(fā)到我們的手機(jī)上。
所以這里也可以通過實(shí)現(xiàn)統(tǒng)一接口來具體實(shí)現(xiàn)不同的方法。
相同部分我可以通過抽象類來完成實(shí)現(xiàn),不同部分可以通過具體的實(shí)現(xiàn)類來實(shí)現(xiàn)。
AbstractValidateCodeProcessorService 抽象類是用來實(shí)現(xiàn)兩種驗(yàn)證碼可以抽離的部分。ImageCodeProcessorServiceImpl 和
SmsCodeProcessorServiceImpl方法是來實(shí)現(xiàn)兩種驗(yàn)證碼不同的發(fā)送方式。
在簡(jiǎn)單看下時(shí)序圖可能會(huì)更加明白點(diǎn)。
一個(gè)接口只有一個(gè)方法(processor)就是處理驗(yàn)證碼,它其實(shí)需要做三件事。
1、獲取驗(yàn)證碼。2、將驗(yàn)證碼存入session。3、將驗(yàn)證碼信息通過短信或者圖形驗(yàn)證碼發(fā)送出去。
首先講生成獲取驗(yàn)證碼,這里有一個(gè)公共接口和兩個(gè)實(shí)現(xiàn)類
對(duì)于保存驗(yàn)證碼信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象類來完成,都不需要去實(shí)現(xiàn)。
對(duì)發(fā)送驗(yàn)證碼信息而言,只需要實(shí)現(xiàn)AbstractValidateCodeProcessorService抽象類的send發(fā)送驗(yàn)證碼接口即可。
整個(gè)大致接口設(shè)計(jì)就是這樣,具體的可以通過代碼來展示。
二、代碼實(shí)現(xiàn)
1、驗(yàn)證碼屬性
短信驗(yàn)證碼和圖形驗(yàn)證后包含屬性有 code
和 expireTime
,短信驗(yàn)證碼只有這兩個(gè)屬性,而圖形驗(yàn)證碼還多一個(gè)BufferedImage
實(shí)例對(duì)象屬性,所以將共同屬性進(jìn)行抽取
,抽取為ValidateCode類,代碼如下:
ValidateCode實(shí)體
@Data @AllArgsConstructor @NoArgsConstructor public class ValidateCode { private String code; private LocalDateTime expireTime; public boolean isExpired() { return LocalDateTime.now().isAfter(expireTime); } }
對(duì)于圖形驗(yàn)證碼而言,除了需要code和過期時(shí)間還需要圖片的畫布,所以繼承ValidateCode之后再寫自己屬性
ImageCode實(shí)體
@Data public class ImageCode extends ValidateCode { private BufferedImage image; public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { super(code, expireTime); this.image = image; } public ImageCode(BufferedImage image, String code, int expireIn) { super(code, LocalDateTime.now().plusSeconds(expireIn)); this.image = image; } }
對(duì)于短信驗(yàn)證碼而言,暫時(shí)不需要添加自己的屬性字段了。
SmsCode實(shí)體
public class SmsCode extends ValidateCode { public SmsCode(String code, LocalDateTime expireTime) { super(code, expireTime); } public SmsCode(String code, int expireIn) { super(code, LocalDateTime.now().plusSeconds(expireIn)); } }
2、ValidateCodeProcessor接口
ValidateCodeProcessor接口主要是完成 驗(yàn)證碼的生成、保存與發(fā)送的完整流程,接口的主要設(shè)計(jì)如下所示:
ValidateCodeProcessorService接口
public interface ValidateCodeProcessorService { /** * 因?yàn)楝F(xiàn)在有兩種驗(yàn)證碼,所以存放到seesion的key不能一樣,所以前綴+具體type */ String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_"; /** * 通過也是 type+CODE_PROCESSOR獲取對(duì)于的bean */ String CODE_PROCESSOR = "CodeProcessorService"; /** * 這個(gè)接口要做三件事 * 1、獲取驗(yàn)證碼。 * 2、將驗(yàn)證碼存入session * 3、將驗(yàn)證碼信息通過短信或者圖形驗(yàn)證碼發(fā)送出去。 * (將spring-boot-security-study-03接口里的那三步進(jìn)行里封裝) * */ void processor(ServletWebRequest request) throws Exception;
由于圖片驗(yàn)證碼和短信驗(yàn)證碼的 生成和保存、發(fā)送等流程是固定的。所以這里寫一個(gè)抽象類來實(shí)現(xiàn)ValidateCodeProcessor接口,來實(shí)現(xiàn)相似部分。
AbstractValidateCodeProcessorService抽象類
@Component public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService { private static final String SEPARATOR = "/code/"; /** * 操作session的工具集 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); /** * 這是Spring的一個(gè)特性,就是在項(xiàng)目啟動(dòng)的時(shí)候會(huì)自動(dòng)收集系統(tǒng)中 {@link ValidateCodeGeneratorService} 接口的實(shí)現(xiàn)類對(duì)象 * * key為bean name */ @Autowired private Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap; @Override public void processor(ServletWebRequest request) throws Exception { //第一件事 C validateCode = generate(request); //第二件事 save(request, validateCode); //第三件事 send(request, validateCode); } /** * 生成驗(yàn)證碼 * */ private C generate(ServletWebRequest request) { String type = getProcessorType(request); //這里 image+CodeGenerator = imgCodeGenerator 對(duì)應(yīng)的就是ImageCodeGeneratorServiceService ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR)); return (C) validateCodeGenerator.generate(request); } /** * 保存驗(yàn)證碼到session中 */ private void save(ServletWebRequest request, C validateCode) { //這里也是封裝了一下 sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode); } /** * 發(fā)送驗(yàn)證碼 (只有發(fā)送驗(yàn)證碼是需要自己去實(shí)現(xiàn)的。) */ protected abstract void send(ServletWebRequest request, C validateCode) throws Exception; /** * 獲取請(qǐng)求URL中具體請(qǐng)求的驗(yàn)證碼類型 * */ private String getProcessorType(ServletWebRequest request) { // 獲取URI分割后的第二個(gè)片段 (/code/image 通過/code/ 切割后就只剩下 image return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR); } }
簡(jiǎn)單說明
:
1、這里用到了Spring一個(gè)特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 可以把ValidateCodeGeneratorService所以的實(shí)現(xiàn)類都放
到這個(gè)map中,key為bean的名稱。
2、抽象類中實(shí)現(xiàn)了 ValidateCodeProcessor接口的processor方法,它主要是完成了驗(yàn)證碼的創(chuàng)建、保存和發(fā)送的功能。
3、generate 方法根據(jù)傳入的不同泛型而生成了特定的驗(yàn)證碼。
4、save 方法是將生成的驗(yàn)證碼實(shí)例對(duì)象存入到session中,兩種驗(yàn)證碼的存儲(chǔ)方式一致,只是有個(gè)key不一致,所以代碼也是通用的。
5、send 方法一個(gè)抽象方法,分別由ImageCodeProcessorService和SmsCodeProcessorService來具體實(shí)現(xiàn),也是根據(jù)泛型來判斷具體調(diào)用哪一個(gè)具體的實(shí)現(xiàn)類的send方法。
3、驗(yàn)證碼的生成接口
上面說過驗(yàn)證的生成應(yīng)該也是通過實(shí)現(xiàn)類
ValidateCodeGeneratorService
public interface ValidateCodeGeneratorService { /** * 這個(gè)常量也是用來 type+CodeGeneratorService獲取對(duì)于bean對(duì)象 */ String CODE_GENERATOR = "CodeGeneratorService"; /** * 生成驗(yàn)證碼 * 具體是圖片驗(yàn)證碼 還是短信驗(yàn)證碼就需要對(duì)應(yīng)的實(shí)現(xiàn)類 */ ValidateCode generate(ServletWebRequest request); }
圖形驗(yàn)證碼具體實(shí)現(xiàn)類
mageCodeGeneratorServiceImpl
@Data @Component("imageCodeGeneratorService") public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService { private static final String IMAGE_WIDTH_NAME = "width"; private static final String IMAGE_HEIGHT_NAME = "height"; private static final Integer MAX_COLOR_VALUE = 255; @Autowired private ValidateCodeProperties validateCodeProperties; @Override public ImageCode generate(ServletWebRequest request) { int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, validateCodeProperties.getImage().getWidth()); int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME,validateCodeProperties.getImage().getHeight()); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); Random random = new Random(); // 生成畫布 g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); g.setColor(getRandColor(160, 200)); for (int i = 0; i < 155; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } // 生成數(shù)字驗(yàn)證碼 StringBuilder sRand = new StringBuilder(); for (int i = 0; i < validateCodeProperties.getImage().getLength(); i++) { String rand = String.valueOf(random.nextInt(10)); sRand.append(rand); g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); g.drawString(rand, 13 * i + 6, 16); } g.dispose(); return new ImageCode(image, sRand.toString(), validateCodeProperties.getImage().getExpireIn()); } /** * 生成隨機(jī)背景條紋 * * @param fc 前景色 * @param bc 背景色 * @return RGB顏色 */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > MAX_COLOR_VALUE) { fc = MAX_COLOR_VALUE; } if (bc > MAX_COLOR_VALUE) { bc = MAX_COLOR_VALUE; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
短信驗(yàn)證碼具體實(shí)現(xiàn)類
SmsCodeGeneratorServiceImpl
@Data @Component("smsCodeGeneratorService") public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService { @Autowired private ValidateCodeProperties validateCodeProperties; @Override public SmsCode generate(ServletWebRequest request) { //生成隨機(jī)數(shù) String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength()); return new SmsCode(code, validateCodeProperties.getSms().getExpireIn()); } }
4、驗(yàn)證碼的發(fā)送邏輯類
獲取的實(shí)現(xiàn)類寫好了,我們?cè)趯懓l(fā)送具體的發(fā)送實(shí)現(xiàn)類,發(fā)送類的實(shí)現(xiàn)類是實(shí)現(xiàn)AbstractValidateCodeProcessorService抽象類的。
圖片發(fā)送實(shí)現(xiàn)類
ImageCodeProcessorServiceImpl
@Component("imageCodeProcessorService") public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> { private static final String FORMAT_NAME = "JPEG"; /** * 發(fā)送圖形驗(yàn)證碼,將其寫到相應(yīng)中 * * @param request ServletWebRequest實(shí)例對(duì)象 * @param imageCode 驗(yàn)證碼 */ @Override protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception { ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream()); } }
短信發(fā)送具體實(shí)現(xiàn)類。這里只是后臺(tái)輸出就好了,實(shí)際中只要接入對(duì)于的SDK就可以了。
SmsCodeProcessorServiceImpl
@Component("smsCodeProcessorService") public class SmsCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<SmsCode> { private static final String SMS_CODE_PARAM_NAME = "mobile"; @Override protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception { //這里有一個(gè)參數(shù)也是前端需要傳來的 就是用戶的手機(jī)號(hào) String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME); // 這里僅僅寫個(gè)打印,具體邏輯一般都是調(diào)用第三方接口發(fā)送短信 System.out.println("向手機(jī)號(hào)為:" + mobile + "的用戶發(fā)送驗(yàn)證碼:" + smsCode.getCode()); }
整個(gè)大致就是這樣,我們?cè)賮頊y(cè)試一下。
三、測(cè)試
1、ValidateCodeController接口
獲取驗(yàn)證碼接口
@RestController @Slf4j public class ValidateCodeController { @Autowired private Map<String, ValidateCodeProcessorService> validateCodeProcessorMap; @RequestMapping("/code/{type}") public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception { if(!StringUtils.equalsAny(type, "image", "sms")){ log.info("type類型錯(cuò)誤 type={}",type); return; }; //根據(jù)type獲取具體的實(shí)現(xiàn)類 ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR)); validateCodeProcessorService.processor(new ServletWebRequest(request, response)); } }
2、獲得圖形驗(yàn)證碼
獲取成功
3、獲取短信驗(yàn)證碼
獲取短信驗(yàn)證碼需要多傳一個(gè)參數(shù)就是mobile 手機(jī)號(hào)碼
因?yàn)檫@里發(fā)送短信沒有接第三方SDK,而是直接在控制臺(tái)輸出
以上就是關(guān)于SpringSceurity如何實(shí)現(xiàn)短信驗(yàn)證碼功能的內(nèi)容,如果你們有學(xué)習(xí)到知識(shí)或者技能,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。