前言
在前兩個章節中,一一哥 帶大家學習了Spring Security內部關于認證授權的核心API,以及認證授權的執行流程和底層原理。掌握了這些之后,對于Spring Security,我們不僅做到了 "知其然",而且也做到了 "知其所以然"!
在現在的求職環境下,只知道某個技能點的用法是遠遠不夠的,面試官會要求我們研究某個技術的底層實現原理,所以雖然前面的兩章內容掌握起來很有難度,但是還是希望各位小伙伴結合源碼認真研讀,這樣你才能在編程之路上走的更遠更高!
總是研究底層,對于我們初學者來說,既有難度,也會影響咱們的學習積極性,所以從本篇文章開始,咱們繼續學習Spring Security的其他用法,比如我們學習一下如何在Spring Security中添加執行自定義的過濾器,進而實現驗證碼校驗功能。
一. 驗證碼簡介
在進行驗證碼編碼實現之前,壹哥 先給各位介紹一下驗證碼的概念和作用。
驗證碼(CAPTCHA):全稱是Completely Automated Public Turing test to tell Computers and Humans Apart,翻譯過來就是“全自動區分計算機和人類的圖靈測試”。通俗的講,驗證碼就是為了防止惡意用戶采用暴力重試的攻擊手段而設置的一種防護措施。我們在進行用戶注冊、登錄、或者論壇發帖時都可以利用驗證碼,對某些惡意用戶利用計算機發起無限重試進行必要的攔截限制。
接下來在Spring Security的環境中,我們可以用如下兩種方案實現圖形驗證碼:
- 基于自定義的過濾器來實現圖形驗證碼;
- 基于自定義的認證器來實現圖形驗證碼。
在本篇文章中,壹哥 先利用第一種方案,也就是基于自定義的過濾器的方式,來教會大家如何實現圖形驗證碼,請繼續往下看哦。
二. 基于過濾器實現圖形驗證碼
1. 實現概述
在Spring Security中,實現驗證碼校驗的方式有很多種,其中基于過濾器來實現圖形驗證碼是最簡單的方式。小伙伴可能會問,過濾器如何實現驗證碼校驗功能呢?基于什么原理?這個其實很簡單,流程原理就是我們先自定義一個過濾器Filter,處理驗證碼的驗證邏輯,然后把該過濾器添加到Spring Security過濾器鏈的某個合適位置。當匹配到登錄請求時,過濾器會對驗證碼進行校驗,如果成功則放行,如果失敗則結束當前驗證請求。
明白了實現流程和原理后,接下來我們就在之前項目的基礎之上,進行驗證碼功能的代碼實現。
2. 創建新模塊
我們可以創建一個新的項目model,創建過程與之前一樣,這里就略過了。
3. 添加依賴包
本案例中,我們采用github上的開源驗證碼解決方案kaptcha,所以需要在原有項目的基礎上添加kaptcha的依賴包。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>
4. 創建Producer對象
在準備好依賴包之后,接下來我們創建一個CaptchaConfig配置類,在該類中創建一個Producer對象,對驗證碼對象進行必要的配置,比如設置驗證碼背景框的寬度和高度,驗證碼的字符集,驗證碼的個數等。
@Configuration public class CaptchaConfig { @Bean public Producer captcha() { // 配置圖形驗證碼的基本參數 Properties properties = new Properties(); // 圖片寬度 properties.setProperty("kaptcha.image.wth", "150"); // 圖片長度 properties.setProperty("kaptcha.image.height", "50"); // 字符集 properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); // 字符長度 properties.setProperty("kaptcha.textproducer.char.length", "4"); Config config = new Config(properties); // 使用默認的圖形驗證碼實現,當然也可以自定義實現 DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
5. 創建生成驗證碼的接口
在上面創建了Producer對象后,接著我們再創建一個生成驗證碼的接口,該接口中負責生成驗證碼圖片,并且記得要把生成的驗證碼存放到session中,以便后面發起登錄請求時進行比對驗證碼。
@Controller public class CaptchaController { @Autowired private Producer captchaProducer; @GetMapping("/captcha.jpg") public vo getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { // 設置內容類型 response.setContentType("image/jpeg"); // 創建驗證碼文本 String capText = captchaProducer.createText(); // 將驗證碼文本設置到session request.getSession().setAttribute("captcha", capText); // 創建驗證碼圖片 BufferedImage bi = captchaProducer.createImage(capText); // 獲取響應輸出流 ServletOutputStream out = response.getOutputStream(); // 將圖片驗證碼數據寫到響應輸出流 ImageIO.write(bi, "jpg", out); // 推送并關閉響應輸出流 try { out.flush(); } finally { out.close(); } } }
6. 自定義異常
我們可以自定義一個運行時異常,用于處理驗證碼校驗失敗時拋出異常提示信息,當然這個并不是必須的。
public class VerificationCodeException extends AuthenticationException { public VerificationCodeException () { super("圖形驗證碼校驗失敗"); } }
7. 創建攔截驗證碼的過濾器
創建驗證碼并保存到session之后,另一個很重要的工作就是比對用戶從前端傳遞過來的驗證碼是否相同,這個比對工作我們就在自定義的過濾器中進行實現。所以接著我們就創建一個過濾器,負責對用戶發來的驗證碼進行攔截校驗,看看request請求中傳遞過來的驗證碼,與我們生成并保存在session中的驗證碼是否一致。
/** * 基于過濾器實現圖形驗證碼的驗證功能,這屬于Servlet層面,簡單易理解. */ public class VerificationCodeFilter extends OncePerRequestFilter { private AuthenticationFailureHandler authenticationFailureHandler = new SecurityAuthenticationFailureHandler(); @Overre protected vo doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { // 非登錄請求不校驗驗證碼,直接放行 if (!"/login".equals(httpServletRequest.getRequestURI())) { filterChain.doFilter(httpServletRequest, httpServletResponse); } else { try { //校驗驗證碼 verificationCode(httpServletRequest); //驗證碼校驗通過后,對請求進行放行 filterChain.doFilter(httpServletRequest, httpServletResponse); } catch (VerificationCodeException e) { authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e); } } } public vo verificationCode (HttpServletRequest httpServletRequest) throws VerificationCodeException { HttpSession session = httpServletRequest.getSession(); String savedCode = (String) session.getAttribute("captcha"); if (!StringUtils.isEmpty(savedCode)) { // 隨手清除驗證碼,不管是失敗還是成功,所以客戶端應在登錄失敗時刷新驗證碼 session.removeAttribute("captcha"); } String requestCode = httpServletRequest.getParameter("captcha"); // 校驗不通過拋出異常 if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(savedCode) || !requestCode.equals(savedCode)) { throw new VerificationCodeException(); } } }
8. 編寫SecurityConfig
接下來我們需要編寫一個SecurityConfig配置類,在該配置類中,把咱們前面編寫的驗證碼過濾器添加在默認的UsernamePasswordAuthenticationFilter過濾器之前來執行,可以利用http.addFilterBefore()方法來實現。
對于UsernamePasswordAuthenticationFilter過濾器,你還能想起來嗎?如果想不起來,請看我前一篇文章,里面有詳細講解哦!
/** * @Author: 一一哥 * 增加圖形驗證碼功能. */ @EnableWebSecurity(debug = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Overre protected vo configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/api/**") .hasRole("ADMIN") .antMatchers("/user/api/**") .hasRole("USER") .antMatchers("/app/api/**", "/captcha.jpg") .permitAll() .anyRequest() .authenticated() .and() .formLogin() .failureHandler(new SecurityAuthenticationFailureHandler()) .successHandler(new SecurityAuthenticationSuccessHandler()) .loginPage("/myLogin.html") .loginProcessingUrl("/login") .permitAll() .and() .csrf() .disable(); //將過濾器添加在UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
在這里,我們把自定義的驗證碼校驗過濾器VerificationCodeFilter,添加到了Spring Security自帶的UsernamePasswordAuthenticationFilter過濾器前面。
注:
關于我們測試的web接口,數據庫配置、認證成功、失敗時的Handler處理器等,請參考前面《基于Spring Security前后端分離的權限控制系統問題》案例,此處略過。
9. 編寫測試頁面
既然要實現驗證碼功能,為了方便測試,我們需要編寫一個自定義的登錄頁面,并在這里添加對驗證碼接口的引用,這里列出html頁面的核心代碼。
<body> <div class="login"> <h2>Access Form</h2> <div class="login-top"> <h1>登錄驗證</h1> <form action="/login" method="post"> <input type="text" name="username" placeholder="username" /> <input type="password" name="password" placeholder="password" /> <div style="display: flex;"> <!-- 新增圖形驗證碼的輸入框 --> <input type="text" name="captcha" placeholder="captcha" /> <!-- 圖片指向圖形驗證碼API --> <img src="/captcha.jpg" </div> <div class="forgot"> <a href="#">忘記密碼</a> <input type="submit" value="登錄" > </div> </form> </div> <div class="login-bottom"> <h3>新用戶 <a href="#">注 冊</a></h3> </div> </div> </body>
10. 代碼結構
整個項目的代碼結構如下,請參考下圖進行實現。
11. 啟動項目測試
接下來我們啟動項目,在訪問受限接口時,會重定向到myLogin.html登錄頁面,可以看到我們的驗證碼效果已經顯示出來了。
接下來我們輸入正確的用戶名、密碼、驗證碼后,就可以成功的登錄進去訪問web接口了。
至此,基于自定義過濾器實現的驗證碼功能,壹哥 就帶各位實現完畢,你學會了嗎?如有疑問,可以在評論區留言。
到此這篇關于Spring Security基于過濾器實現圖形驗證碼功能的文章就介紹到這了,更多相關Spring Security圖形驗證碼內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/syc000666/article/details/120481479