有關SpringSceurity系列以前有寫文章html
一、SpringSecurity(1)---認證+受權代碼實現前端
二、SpringSecurity(2)---記住我功能實現java
三、SpringSceurity(3)---圖形驗證碼功能實現spring
在獲取短信驗證碼功能和圖形驗證碼仍是有不少類似的地方,因此這裏在設計獲取短信驗證的時候,將以前開發好的的圖形驗證碼進一步整合、抽象與重構。session
在獲取驗證碼的時候,它們最大的不一樣在於: 圖形驗證碼是經過接口返回獲取給前端。而短信驗證碼而言是經過第三方API向咱們手機推送。架構
可是它們在登錄的時候就有很大的不一樣了,對於圖形驗證碼而言驗證經過以前就走 UsernamePasswordAuthenticationFilter 過濾器了開始校驗用戶名密碼了。app
但對於短信登錄而言,確實也須要先如今短信驗證碼是否經過,可是一旦經過他是不走 UsernamePasswordAuthenticationFilter,而是經過其它方式查詢用戶信息來校驗dom
認證已經經過了。ide
這篇博客只寫獲取獲取短信驗證碼的功能,不寫經過短信驗證碼登錄的邏輯。spring-boot
這裏纔是最重要的,如何去設計和整合短信驗證碼和圖形驗證碼的代碼,是咱們最應該思考的。如何將類似部分抽離出來,而後去實現不相同的部分。
整理後發現不一樣點主要在於
一、獲取驗證碼。由於對於圖形驗證碼須要有個畫布,而短信驗證碼並不須要,因此它們能夠實現同一個接口,來完成不一樣的邏輯。 二、發送驗證碼。對於圖形驗證碼來說只要把驗證碼返給前端就能夠,而短信驗證碼而言是經過第三方API將驗證碼發到咱們的手機上。 因此這裏也能夠經過實現統一接口來具體實現不一樣的方法。
相同部分我能夠經過抽象類來完成實現,不一樣部分能夠經過具體的實現類來實現。
AbstractValidateCodeProcessorService 抽象類是用來實現兩種驗證碼能夠抽離的部分。ImageCodeProcessorServiceImpl 和
SmsCodeProcessorServiceImpl方法是來實現兩種驗證碼不一樣的發送方式。
在簡單看下時序圖可能會更加明白點。
一個接口只有一個方法(processor)就是處理驗證碼,它其實須要作三件事。
一、獲取驗證碼。二、將驗證碼存入session。三、將驗證碼信息經過短信或者圖形驗證碼發送出去。
首先講生成獲取驗證碼,這裏有一個公共接口和兩個實現類
對於保存驗證碼信息而言,能夠在直接在 AbstractValidateCodeProcessorService抽象類來完成,都不須要去實現。
對發送驗證碼信息而言,只須要實現AbstractValidateCodeProcessorService抽象類的send發送驗證碼接口便可。
整個大體接口設計就是這樣,具體的能夠經過代碼來展現。
短信驗證碼和圖形驗證後包含屬性有 code
和 expireTime
,短信驗證碼只有這兩個屬性,而圖形驗證碼還多一個BufferedImage
實例對象屬性,因此將共同屬性進行抽取
,抽取爲ValidateCode類,代碼以下:
ValidateCode實體
@Data @AllArgsConstructor @NoArgsConstructor public class ValidateCode { private String code; private LocalDateTime expireTime; public boolean isExpired() { return LocalDateTime.now().isAfter(expireTime); } }
對於圖形驗證碼而言,除了須要code和過時時間還須要圖片的畫布,因此繼承ValidateCode以後再寫本身屬性
ImageCode實體
@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; } }
對於短信驗證碼而言,暫時不須要添加本身的屬性字段了。
SmsCode實體
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)); } }
ValidateCodeProcessor接口主要是完成 驗證碼的生成、保存與發送的完整流程,接口的主要設計以下所示:
ValidateCodeProcessorService接口
public interface ValidateCodeProcessorService { /** * 由於如今有兩種驗證碼,因此存放到seesion的key不能同樣,因此前綴+具體type */ String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_"; /** * 經過也是 type+CODE_PROCESSOR獲取對於的bean */ String CODE_PROCESSOR = "CodeProcessorService"; /** * 這個接口要作三件事 * 一、獲取驗證碼。 * 二、將驗證碼存入session * 三、將驗證碼信息經過短信或者圖形驗證碼發送出去。 * (將spring-boot-security-study-03接口裏的那三步進行裏封裝) * */ void processor(ServletWebRequest request) throws Exception;
因爲圖片驗證碼和短信驗證碼的 生成和保存、發送等流程是固定的。因此這裏寫一個抽象類來實現ValidateCodeProcessor接口,來實現類似部分。
AbstractValidateCodeProcessorService抽象類
@Component public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService { private static final String SEPARATOR = "/code/"; /** * 操做session的工具集 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); /** * 這是Spring的一個特性,就是在項目啓動的時候會自動收集系統中 {@link ValidateCodeGeneratorService} 接口的實現類對象 * * 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); } /** * 生成驗證碼 * */ private C generate(ServletWebRequest request) { String type = getProcessorType(request); //這裏 image+CodeGenerator = imgCodeGenerator 對應的就是ImageCodeGeneratorServiceService ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR)); return (C) validateCodeGenerator.generate(request); } /** * 保存驗證碼到session中 */ private void save(ServletWebRequest request, C validateCode) { //這裏也是封裝了一下 sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode); } /** * 發送驗證碼 (只有發送驗證碼是須要本身去實現的。) */ protected abstract void send(ServletWebRequest request, C validateCode) throws Exception; /** * 獲取請求URL中具體請求的驗證碼類型 * */ private String getProcessorType(ServletWebRequest request) { // 獲取URI分割後的第二個片斷 (/code/image 經過/code/ 切割後就只剩下 image return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR); } }
簡單說明
:
一、這裏用到了Spring一個特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 能夠把ValidateCodeGeneratorService因此的實現類都放
到這個map中,key爲bean的名稱。
二、抽象類中實現了 ValidateCodeProcessor接口的processor方法,它主要是完成了驗證碼的建立、保存和發送的功能。
三、generate 方法根據傳入的不一樣泛型而生成了特定的驗證碼。
四、save 方法是將生成的驗證碼實例對象存入到session中,兩種驗證碼的存儲方式一致,只是有個key不一致,因此代碼也是通用的。
五、send 方法一個抽象方法,分別由ImageCodeProcessorService和SmsCodeProcessorService來具體實現,也是根據泛型來判斷具體調用哪個具體的實現類的send方法。
上面說過驗證的生成應該也是經過實現類
ValidateCodeGeneratorService
public interface ValidateCodeGeneratorService { /** * 這個常量也是用來 type+CodeGeneratorService獲取對於bean對象 */ String CODE_GENERATOR = "CodeGeneratorService"; /** * 生成驗證碼 * 具體是圖片驗證碼 仍是短信驗證碼就須要對應的實現類 */ ValidateCode generate(ServletWebRequest request); }
圖形驗證碼具體實現類
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); } // 生成數字驗證碼 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()); } /** * 生成隨機背景條紋 * * @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); } }
短信驗證碼具體實現類
SmsCodeGeneratorServiceImpl
@Data @Component("smsCodeGeneratorService") public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService { @Autowired private ValidateCodeProperties validateCodeProperties; @Override public SmsCode generate(ServletWebRequest request) { //生成隨機數 String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength()); return new SmsCode(code, validateCodeProperties.getSms().getExpireIn()); } }
獲取的實現類寫好了,咱們在寫發送具體的發送實現類,發送類的實現類是實現AbstractValidateCodeProcessorService抽象類的。
圖片發送實現類
ImageCodeProcessorServiceImpl
@Component("imageCodeProcessorService") public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> { private static final String FORMAT_NAME = "JPEG"; /** * 發送圖形驗證碼,將其寫到相應中 * * @param request ServletWebRequest實例對象 * @param imageCode 驗證碼 */ @Override protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception { ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream()); } }
短信發送具體實現類。這裏只是後臺輸出就行了,實際中只要接入對於的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 { //這裏有一個參數也是前端須要傳來的 就是用戶的手機號 String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME); // 這裏僅僅寫個打印,具體邏輯通常都是調用第三方接口發送短信 System.out.println("向手機號爲:" + mobile + "的用戶發送驗證碼:" + smsCode.getCode()); }
整個大體就是這樣,咱們再來測試一下。
獲取驗證碼接口
@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類型錯誤 type={}",type); return; }; //根據type獲取具體的實現類 ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR)); validateCodeProcessorService.processor(new ServletWebRequest(request, response)); } }
獲取成功
獲取短信驗證碼須要多傳一個參數就是mobile 手機號碼
由於這裏發送短信沒有接第三方SDK,而是直接在控制檯輸出
一、Spring Security技術棧開發企業級認證與受權(JoJo)
這一套架構設計真的很是的好,代碼可讀性和可用性都很是高,之後若是要接第三個驗證碼只要實現發送和獲取的接口來自定義實a現就行了。很受啓發!
別人罵我胖,我會生氣,由於我內心認可了我胖。別人說我矮,我就會以爲可笑,由於我內心知道我不可能矮。這就是咱們爲何會對別人的攻擊生氣。 攻我盾者,乃我心裏之矛(20)