SpringBoot整合SpringSecurity實現(xiàn)圖形驗證碼功能
1、圖形驗證碼的作用
圖形驗證碼(CAPTCHA,Completely Automated Public Turing test to tell Computers and Humans Apart)是一種用于區(qū)分用戶是人類還是計算機程序的自動化測試。它通常用于防止自動化軟件(如機器人或爬蟲程序)進行惡意操作,如濫用在線服務(wù)、暴力破 解密碼或進行垃圾郵件發(fā)送等。
圖形驗證碼的工作原理基于一個假設(shè):計算機程序難以自動識別和處理復(fù)雜的圖像或模式,而人類則相對容易。因此,圖形驗證碼通常包含扭曲的文字、數(shù)字、圖像或它們的組合,這些元素對人類來說相對容易辨認,但對計算機程序來說則非常困難。
下面將介紹 Spring Boot 整合 Spring Security 實現(xiàn)圖形驗證碼功能,執(zhí)行結(jié)果如下如:
(1)登錄頁面
(2)登錄成功后,跳轉(zhuǎn)至首頁
2、創(chuàng)建項目
【示例】SpringBoot 整合 SpringSecurity 使用過濾器實現(xiàn)圖形驗證碼功能。
2.1 創(chuàng)建 Spring Boot 項目
創(chuàng)建 SpringBoot 項目,項目結(jié)構(gòu)如下圖:
2.2 添加 Maven 依賴
在 pom.xml 配置文件中添加 Spring Security、谷歌 Kaptcha 圖形驗證碼。
<!-- Spring Security 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.7.18</version> </dependency> <!-- 谷歌 Kaptcha 圖形驗證碼 --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
3、整合 Spring Security 框架實現(xiàn)認證與授權(quán)
3.1 配置類(Config 層)
創(chuàng)建 WebSecurityConfig 類(Spring Security 配置類),并添加 @EnableWebSecurity 注解和繼承 WebSecurityConfigurerAdapter 類。
package com.pjb.securitydemo.config; import com.pjb.securitydemo.filter.VerificationCodeFilter; import com.pjb.securitydemo.handler.LoginFailureHandler; import com.pjb.securitydemo.handler.LoginSuccessHandler; import com.pjb.securitydemo.handler.PermissionDeniedHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * Spring Security 配置類 * @author pan_junbiao **/ @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private LoginSuccessHandler loginSuccessHandler; @Autowired private LoginFailureHandler loginFailureHandler; @Autowired private PermissionDeniedHandler permissionDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //返回一個URL攔截注冊器 .antMatchers("/captcha.jpg").permitAll() //公開其權(quán)限 .anyRequest() //匹配所有的請求 .authenticated() //所有匹配的URL都需要被認證才能訪問 .and() //結(jié)束當前標簽,讓上下文回到 HttpSecurity .formLogin() //啟動表單認證 .loginPage("/myLogin.html") //自定義登錄頁面 .loginProcessingUrl("/auth/form") //指定處理登錄請求路徑 .permitAll() //使登錄頁面不設(shè)限訪問 //.defaultSuccessUrl("/index") //登錄認證成功后的跳轉(zhuǎn)頁面 .successHandler(loginSuccessHandler) //指定登錄成功時的處理 .failureHandler(loginFailureHandler) //指定登錄失敗時的處理 .and() .exceptionHandling().accessDeniedHandler(permissionDeniedHandler) //403無權(quán)時的返回操作 .and().csrf().disable(); //關(guān)閉CSRF的防御功能 //圖形驗證碼過濾器(核心代碼):將自定義過濾器添加在UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class); } /** * 內(nèi)存中添加登錄賬號 */ @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("admin").password("123456").roles("ADMIN").build()); manager.createUser(User.withUsername("user").password("123456").roles("USER").build()); manager.createUser(User.withUsername("panjunbiao").password("123456").roles("USER").build()); return manager; } /** * 密碼編譯器 * 由于5.x版本之后默認啟用了委派密碼編譯器, * 因而按照以往的方式設(shè)置內(nèi)存密碼將會讀取異常, * 所以需要暫時將密碼編碼器設(shè)置為 NoOpPasswordEncoder */ @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
3.2 處理類(Handler 層)
(1)登錄成功處理類
package com.pjb.securitydemo.handler; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 登錄成功處理類 */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { //重定向至首頁 httpServletResponse.sendRedirect("/"); } }
(2)登錄失敗處理類
package com.pjb.securitydemo.handler; import com.pjb.securitydemo.exception.VerificationCodeException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 登錄失敗處理類 */ @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException { //獲取登錄失敗原因 String errorMessage = ""; if(authenticationException instanceof BadCredentialsException){ errorMessage = "用戶名或密碼不正確"; }else if(authenticationException instanceof DisabledException){ errorMessage = "賬號被禁用"; }else if(authenticationException instanceof UsernameNotFoundException){ errorMessage = "用戶名不存在"; }else if(authenticationException instanceof CredentialsExpiredException){ errorMessage = "密碼已過期"; }else if(authenticationException instanceof LockedException) { errorMessage = "賬號被鎖定"; }else if(authenticationException instanceof VerificationCodeException){ errorMessage = "無效的圖形驗證碼"; }else{ errorMessage = "未知異常"; } //設(shè)置響應(yīng)編碼 httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write(errorMessage); } }
(3)403無權(quán)限處理類
package com.pjb.securitydemo.handler; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 403無權(quán)限處理類 */ @Component public class PermissionDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write("403無權(quán)限"); } }
4、整合 Kaptcha 框架實現(xiàn)圖形驗證碼
4.1 配置類(Config 層)
創(chuàng)建 KaptchaConfig 類(Kaptcha 圖形驗證碼配置類),設(shè)置圖形驗證碼相關(guān)屬性。
package com.pjb.securitydemo.config; import com.google.code.kaptcha.Producer; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; /** * 谷歌Kaptcha圖形驗證碼配置類 */ @Configuration public class KaptchaConfig { @Bean public Producer captcha() { //配置圖形驗證碼的基本參數(shù) Properties properties = new Properties(); //圖片寬度 properties.setProperty("kaptcha.image.width","150"); //圖片長度 properties.setProperty("kaptcha.image.height","50"); //字符集(從哪些字符中產(chǎn)生) properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); //字符長度 properties.setProperty("kaptcha.textproducer.char.length", "4"); //字體顏色 properties.put("kaptcha.textproducer.font.color", "red"); // 文字間隔,這里設(shè)置為10px properties.put("kaptcha.textproducer.char.space", "10"); // 背景顏色漸變開始 properties.put("kaptcha.background.clear.from", "yellow"); // 背景顏色漸變結(jié)束 properties.put("kaptcha.background.clear.to", "green"); //初始化配置 Config config = new Config(properties); //使用默認的圖形驗證碼實現(xiàn),當然也可以自定義實現(xiàn) DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
圖形驗證碼配置屬性表
屬性名 | 屬性作用 | 默認值 |
---|---|---|
kaptcha.border | 圖片邊框,合法值:yes , no | yes |
kaptcha.border.color | 邊框顏色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black |
kaptcha.image.width | 圖片寬 | 200 |
kaptcha.image.height | 圖片高 | 50 |
kaptcha.producer.impl | 圖片實現(xiàn)類 | com.google.code.kaptcha.impl.DefaultKaptcha |
kaptcha.textproducer.impl | 文本實現(xiàn)類 | com.google.code.kaptcha.text.impl.DefaultTextCreator |
kaptcha.textproducer.char.string | 文本集合,驗證碼值從此集合中獲取 | abcde2345678gfynmnpwx |
kaptcha.textproducer.char.length | 驗證碼長度 | 5 |
kaptcha.textproducer.font.names | 字體 | Arial, Courier |
kaptcha.textproducer.font.size | 字體大小 | 40px. |
kaptcha.textproducer.font.color | 字體顏色,合法值: r,g,b 或者 white,black,blue. | black |
kaptcha.textproducer.char.space | 文字間隔 | 2 |
kaptcha.noise.impl | 干擾實現(xiàn)類 | com.google.code.kaptcha.impl.DefaultNoise |
kaptcha.noise.color | 干擾 顏色,合法值: r,g,b 或者 white,black,blue. | black |
kaptcha.obscurificator.impl | 圖片樣式:<br />水紋 com.google.code.kaptcha.impl.WaterRipple <br /> 魚眼 com.google.code.kaptcha.impl.FishEyeGimpy <br /> 陰影 com.google.code.kaptcha.impl.ShadowGimpy | com.google.code.kaptcha.impl.WaterRipple |
kaptcha.background.impl | 背景實現(xiàn)類 | com.google.code.kaptcha.impl.DefaultBackground |
kaptcha.background.clear.from | 背景顏色漸變,開始顏色 | light grey |
kaptcha.background.clear.to | 背景顏色漸變, 結(jié)束顏色 | white |
kaptcha.word.impl | 文字渲染器 | com.google.code.kaptcha.text.impl.DefaultWordRenderer |
kaptcha.session.key | session key | KAPTCHA_SESSION_KEY |
kaptcha.session.date | session date |
4.2 控制器層(Controller 層)
創(chuàng)建 CaptchaController 類(驗證碼控制器),實現(xiàn)生成驗證碼圖片方法。
package com.pjb.securitydemo.controller; import com.google.code.kaptcha.Producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.IOException; /** * 驗證碼控制器 */ @Controller public class CaptchaController { @Autowired private Producer captchaProducer; @GetMapping("/captcha.jpg") public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { //設(shè)置內(nèi)容類型 response.setContentType("image/jpeg"); //創(chuàng)建驗證碼文本 String capText = captchaProducer.createText(); //將驗證碼文本保存到Session中 request.getSession().setAttribute("captcha", capText); //創(chuàng)建驗證碼圖片 BufferedImage bufferedImage = captchaProducer.createImage(capText); //獲取響應(yīng)輸出流 ServletOutputStream out = response.getOutputStream(); //將圖片驗證碼數(shù)據(jù)寫入響應(yīng)輸出流 ImageIO.write(bufferedImage,"jpg",out); //推送并關(guān)閉響應(yīng)輸出流 try { out.flush(); } finally { out.close(); } } }
4.3 自定義異常類(Exception 層)
自定義異常類 VerificationCodeException(驗證碼校驗失敗的異常類),繼承 AuthenticationException 類。
package com.pjb.securitydemo.exception; import org.springframework.security.core.AuthenticationException; /** * 驗證碼校驗失敗的異常類 */ public class VerificationCodeException extends AuthenticationException { public VerificationCodeException() { super("圖形驗證碼校驗失敗"); } }
4.4 自定義過濾器(Filter 層)
自定義過濾器類 VerificationCodeFilter (驗證碼校驗過濾器),繼承 OncePerRequestFilter 類。
有了圖形驗證碼的 API 之后,就可以自定義驗證碼校驗過濾器了。雖然 Spring Security 的過濾器鏈對過濾器沒有特殊要求,只要繼承 Filter 接口即可,但是在 Spring 體系中,推薦使用 OncePerRequestFilter 類來實現(xiàn),它可以確保一次請求只會通過一次該過濾器(Filter 實際上并不能保證這一點)。
package com.pjb.securitydemo.filter; import com.pjb.securitydemo.exception.VerificationCodeException; import com.pjb.securitydemo.handler.LoginFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; /** * 驗證碼校驗過濾器 */ public class VerificationCodeFilter extends OncePerRequestFilter { private AuthenticationFailureHandler authenticationFailureHandler = new LoginFailureHandler(); @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { //非登錄請求不校驗驗證碼 String requestURI = httpServletRequest.getRequestURI(); if(!"/auth/form".equals(requestURI)) { filterChain.doFilter(httpServletRequest,httpServletResponse); } else { try { //驗證碼校驗 verificationCode(httpServletRequest); //驗證成功 filterChain.doFilter(httpServletRequest,httpServletResponse); } catch (VerificationCodeException ex) { //驗證失敗 authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, ex); } } } /** * 驗證碼校驗 */ public void verificationCode(HttpServletRequest httpServletRequest) throws VerificationCodeException { String requestCode = httpServletRequest.getParameter("captcha"); HttpSession session = httpServletRequest.getSession(); String savedCode = (String)session.getAttribute("captcha"); if(!StringUtils.isEmpty(savedCode)) { //隨手清除驗證碼,無論是失敗,還是成功??蛻舳藨?yīng)在登錄失敗時刷新驗證碼 session.removeAttribute("captcha"); } //驗證不通過,拋出異常 if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(savedCode) || !requestCode.equals(savedCode)) { throw new VerificationCodeException(); } } }
至此整合 Kaptcha 框架實現(xiàn)圖形驗證碼已完成,最后注意,一定要把自定義過濾器類 VerificationCodeFilter 添加到 Spring Security 的過濾器鏈中。
打開 WebSecurityConfig 類(Spring Security 配置類),將自定義過濾器類 VerificationCodeFilter 添加到過濾器鏈中,如下:
5、前端頁面
5.1 控制器層(Controller 層)
創(chuàng)建 IndexController 類(首頁控制器),實現(xiàn)獲取當前登錄用戶名并跳轉(zhuǎn)至首頁。
package com.pjb.securitydemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import java.security.Principal; /** * 首頁控制器 * @author pan_junbiao **/ @Controller public class IndexController { /** * 首頁 */ @RequestMapping("/") public String index(HttpServletRequest request) { //獲取當前登錄人 String userName = "未登錄"; Principal principal = request.getUserPrincipal(); if(principal!=null) { userName = principal.getName(); } //返回頁面 request.setAttribute("userName",userName); return "/index.html"; } }
5.2 編寫登錄頁面
在 resources\static 靜態(tài)資源目錄下,創(chuàng)建 myLogin.html 頁面。
注意:myLogin.html 頁面必須放在 resources\static 靜態(tài)資源目錄下,否則頁面無法加載。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄</title> <meta name="author" content="pan_junbiao的博客"> </head> <body> <form name="myForm" action="/auth/form" method="post"> <table align="center"> <caption>用戶登錄</caption> <tr> <td>登錄賬戶:</td> <td> <input type="text" name="username" placeholder="請輸入登錄賬戶" value="panjunbiao" /> </td> </tr> <tr> <td>登錄密碼:</td> <td> <input type="password" name="password" placeholder="請輸入登錄密碼" value="123456" /> </td> </tr> <tr> <td>驗證碼:</td> <td> <!-- 新增圖形驗證碼的輸入框 --> <input type="text" name="captcha" placeholder="請輸入驗證碼" /> <!-- 圖片指向圖形驗證碼API --> <img src="/captcha.jpg" alt="captch" height="50px" width="150px" style="margin-left:20px;" > </td> </tr> <!-- 以下是提交、取消按鈕 --> <tr> <td colspan="2" style="text-align: center; padding: 5px;"> <input type="submit" value="提交" /> <input type="reset" value="重置" /> </td> </tr> </table> </form> </body> </html>
5.3 編寫首頁
在 resources\templates 資源目錄下,創(chuàng)建 index.html 頁面。
注意:首頁 index.html 頁面中使用 Thymeleaf 模板 。
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首頁</title> <meta name="author" content="pan_junbiao的博客"> </head> <body> <h1 style="color: red">Hello,Spring Security</h1> <p>博客信息:您好,歡迎訪問 pan_junbiao的博客</p> <p>博客地址:https://blog.csdn.net/pan_junbiao</p> <p th:text="'當前登錄人:' + ${userName}"></p> <a href="/logout" rel="external nofollow" onclick="return confirm('確認注銷嗎?');">登出</a> </body> </html>
6、運行項目
6.1 登錄頁面
6.2 圖形驗證碼校驗失敗
6.3 登錄成功后,跳轉(zhuǎn)至首頁
以上就是SpringBoot整合SpringSecurity實現(xiàn)圖形驗證碼功能的詳細內(nèi)容,更多關(guān)于SpringBoot SpringSecurity圖形驗證碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot validator參數(shù)驗證restful自定義錯誤碼響應(yīng)方式
這篇文章主要介紹了SpringBoot validator參數(shù)驗證restful自定義錯誤碼響應(yīng)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Spring Cloud Alibaba 本地調(diào)試介紹及方案設(shè)計
為了解決 本地調(diào)試 的問題,本文實現(xiàn)了一種簡單實用的策略,可以通過 Nacos 動態(tài)配置服務(wù)路由,還可以基于用戶,部門,組織等級別配置服務(wù)路由,實現(xiàn) 本地調(diào)試 的同時,實際上也實現(xiàn) 灰度發(fā)布,感興趣的朋友跟隨小編一起看看吧2021-07-07java批量下載將多個文件(minio中存儲)壓縮成一個zip包代碼示例
在Java應(yīng)用程序中有時我們需要從多個URL地址下載文件,并將這些文件打包成一個Zip文件進行批量處理或傳輸,這篇文章主要給大家介紹了關(guān)于java批量下載將多個文件(minio中存儲)壓縮成一個zip包的相關(guān)資料,需要的朋友可以參考下2023-11-11Java中l(wèi)ambda表達式實現(xiàn)aop切面功能
本文主要介紹了Java中l(wèi)ambda表達式實現(xiàn)aop切面功能,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02