前言
在上一個章節中,一一哥 帶大家實現了如何在Spring Security中添加執行自定義的過濾器,進而實現驗證碼校驗功能。這種實現方式,只是實現驗證碼功能的方式之一,接下來我們再學習另一種實現方式,就是利用AuthenticationProver來實現驗證碼功能,通過這個案例,我們學習如何進行自定義AuthenticationProver。
一. 認證提供器簡介
在上一章節中,我帶各位利用自定義的過濾器實現了圖形驗證碼效果,接下來我們利用另一種方式,基于自定義的認證提供器來實現圖形驗證碼。
1. 認證提供器AuthenticationProver
在第11章節中,壹哥 給大家講過Spring Security的認證授權實現流程,其中就給大家講解過AuthenticationProver的作用,接下來我們看一下AuthenticationProver接口的類關系圖:
從上圖中可知,AuthenticationProver是一個接口,該接口有一個直接的子類AbstractUserDetailsAuthenticationProver,該類有2個抽象的方法:additionalAuthenticationChecks() 和 retrieveUser(),如下圖:
我們可以通過編寫一個子類繼承AbstractUserDetailsAuthenticationProver,復寫這2個抽象方法,進行滿足自己需求的擴展實現。Spring Security中的DaoAuthenticationProver子類就是通過復寫這2個抽象方法,實現了基于數據庫模型的認證授權。
我們今天會通過繼承DaoAuthenticationProver,來實現圖形驗證碼的校驗功能。
2. WebAuthenticationDetails類介紹
了解完上面的AuthenticationProver類之后,我們還需要了解另一個類WebAuthenticationDetails。
我們知道在Spring Security中有一個UsernamePasswordAuthenticationToken類,封裝了用戶的principal、credentials信息,該類還從它的父類AbstractAuthenticationToken中繼承了details信息。其中這個details信息表示認證用戶的額外信息,比如請求用戶的remoteAddress和sessionId等信息,這兩個信息都是在另一個WebAuthenticationDetails類中定義的,所以我們可以利用WebAuthenticationDetails來封裝用戶的額外信息。
了解完上面的這些必要的API,我們就可以實現今天的需求了。
二. 實現圖形驗證碼
1. 添加依賴包
我們還是和之前的案例一樣,可以先創建一個新的module,創建過程略。
在本案例中我們依然采用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>
2. 創建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; } }
3. 創建生成驗證碼的接口
在上面創建了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(); } } }
4. 自定義異常
接下來自定義一個運行時異常,用于處理驗證碼校驗失敗時拋出異常提示信息。
public class VerificationCodeException extends AuthenticationException { public VerificationCodeException() { super("圖形驗證碼校驗失敗"); } }
5. 自定義WebAuthenticationDetails
我在上面給大家介紹過WebAuthenticationDetails這個類,知道該類中可以封裝用戶的額外信息,所以在這里我們自定義一個WebAuthenticationDetails類,封裝驗證碼信息,并把用戶傳遞過來的驗證碼與session中保存的驗證碼進行對比。
/** * 添加額外的用戶認證信息 */ public class MyWebAuthenticationDetails extends WebAuthenticationDetails { private String imageCode; private String savedImageCode; public String getImageCode() { return imageCode; } public String getSavedImageCode() { return savedImageCode; } /** * 補充用戶提交的驗證碼和session保存的驗證碼 */ public MyWebAuthenticationDetails(HttpServletRequest request) { super(request); this.imageCode = request.getParameter("captcha"); //獲取session對象 HttpSession session = request.getSession(); this.savedImageCode = (String) session.getAttribute("captcha"); if (!StringUtils.isEmpty(this.savedImageCode)) { // 隨手清除驗證碼,不管是失敗還是成功,所以客戶端應在登錄失敗時刷新驗證碼 session.removeAttribute("captcha"); } } }
6. 自定義AuthenticationDetailsSource
AuthenticationDetailsSource是一個接口,該接口帶有一個buildDetails方法,該方法會在創建一個新的authentication的details對象時被調用,而且可以在這里傳遞給details對象一個request參數,如下圖所示:
所以這里我們定義一個AuthenticationDetailsSource類,通過該類構建出上面定義的WebAuthenticationDetails對象,并且給WebAuthenticationDetails傳遞進去HttpServletRequest對象。
@Component public class MyWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest,WebAuthenticationDetails> { /** * 創建一個WebAuthenticationDetails對象 */ @Overre public WebAuthenticationDetails buildDetails(HttpServletRequest request) { return new MyWebAuthenticationDetails(request); } }
7. 自定義DaoAuthenticationProver
接下來通過繼承DaoAuthenticationProver父類,來引入對圖形驗證碼的驗證操作。
/** * 在常規的數據庫認證之上,添加圖形驗證碼功能 */ @Component public class MyAuthenticationProver extends DaoAuthenticationProver { /** * 構造方法注入UserDetailService和PasswordEncoder */ public MyAuthenticationProver(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { this.setUserDetailsService(userDetailsService); this.setPasswordEncoder(passwordEncoder); } /** * 在常規的認證之上,添加額外的圖形驗證碼功能 */ @Overre protected vo additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException { //獲取token令牌中關聯的details對象,并將其轉換為我們自定義的MyWebAuthenticationDetails MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) usernamePasswordAuthenticationToken.getDetails(); String imageCode = details.getImageCode(); String savedImageCode = details.getSavedImageCode(); // 檢驗圖形驗證碼 if (StringUtils.isEmpty(imageCode) || StringUtils.isEmpty(savedImageCode) || !imageCode.equals(savedImageCode)) { throw new VerificationCodeException(); } //在正常的認證檢查之前,添加額外的關于圖形驗證碼的校驗 super.additionalAuthenticationChecks(userDetails, usernamePasswordAuthenticationToken); } }
8. 添加SecurityConfig
然后創建編寫SecurityConfig類,關聯配置我們前面編寫的AuthenticationDetailsSource和AuthenticationProver類。
@SuppressWarnings("all") @EnableWebSecurity(debug = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myWebAuthenticationDetailsSource; @Autowired private AuthenticationProver authenticationProver; @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() //這里關聯配置自定義的AuthenticationDetailsSource .authenticationDetailsSource(myWebAuthenticationDetailsSource) .failureHandler(new SecurityAuthenticationFailureHandler()) .successHandler(new SecurityAuthenticationSuccessHandler()) .loginPage("/myLogin.html") .loginProcessingUrl("/login") .permitAll() .and() .csrf() .disable(); } //在這里關聯我們自定義的AuthenticationProver @Overre protected vo configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProver(authenticationProver); } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
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="#" rel="external nofollow" rel="external nofollow" >忘記密碼</a> <input type="submit" value="登錄" > </div> </form> </div> <div class="login-bottom"> <h3>新用戶 <a href="#" rel="external nofollow" rel="external nofollow" >注 冊</a></h3> </div> </div> </body>
10. 代碼結構
本案例的主要代碼結構如下圖所示,各位可以參考創建。
11. 啟動項目測試
接下來我們啟動項目,跳轉到登錄頁面后,我們就可以看到驗證碼已經被創建出來了。
此時我們可以看到生成的數字驗證碼,在我們輸入正確的用戶名、密碼、驗證碼后,就可以成功的登錄進去訪問web接口了。
至此,我們就實現了基于自定義的認證提供器來實現圖形驗證碼功能了,這種實現方式要比第一種實現方式更復雜一些,其實都能滿足我們的開發需求。有的小伙伴會問,開發時到底選擇哪一種方式呢?壹哥覺得都無所謂的!你有什么更好的見解嗎?可以在評論區留言哦!
到此這篇關于Spring Security基于自定義的認證提供器實現圖形驗證碼的文章就介紹到這了,更多相關Spring Security認證提供器實現圖形驗證碼內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/syc000666/article/details/120481562