Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能
這篇文章主要介紹了Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
在spring security實(shí)現(xiàn)登錄注銷(xiāo)功能的基礎(chǔ)上進(jìn)行開(kāi)發(fā)。
1、添加生成驗(yàn)證碼的控制器。
(1)、生成驗(yàn)證碼
/** * 引入 Security 配置屬性類(lèi) */ @Autowired private SecurityProperties securityProperties; @Override public ImageCode createCode(HttpServletRequest request ) { //如果請(qǐng)求中有 width 參數(shù),則用請(qǐng)求中的,否則用 配置屬性中的 int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getWidth()); //高度(寬度) int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getHeight()); //圖片驗(yàn)證碼字符個(gè)數(shù) int length = securityProperties.getLength(); //過(guò)期時(shí)間 int expireIn = securityProperties.getExpireIn(); 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); } String sRand = ""; for (int i = 0; i < length; i++) { String rand = String.valueOf(random.nextInt(10)); sRand += 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, expireIn); } /** * 生成隨機(jī)背景條紋 */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } 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); }
(2)、驗(yàn)證碼控制器
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; @Autowired private ValidateCodeGenerator imageCodeGenerator; /** * Session 對(duì)象 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @GetMapping("/code/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { ImageCode imageCode = imageCodeGenerator.createCode(request); //將隨機(jī)數(shù) 放到Session中 sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode); request.getSession().setAttribute(SESSION_KEY,imageCode); //寫(xiě)給response 響應(yīng) response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream()); }
(3)、其它輔助類(lèi)
@Data public class ImageCode { /** * 圖片 */ private BufferedImage image; /** * 隨機(jī)數(shù) */ private String code; /** * 過(guò)期時(shí)間 */ private LocalDateTime expireTime; public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { this.image = image; this.code = code; this.expireTime = expireTime; } public ImageCode(BufferedImage image, String code, int expireIn) { this.image = image; this.code = code; //當(dāng)前時(shí)間 加上 設(shè)置過(guò)期的時(shí)間 this.expireTime = LocalDateTime.now().plusSeconds(expireIn); } public boolean isExpried(){ //如果 過(guò)期時(shí)間 在 當(dāng)前日期 之前,則驗(yàn)證碼過(guò)期 return LocalDateTime.now().isAfter(expireTime); } }
@ConfigurationProperties(prefix = "sso.security.code.image") @Component @Data public class SecurityProperties { /** * 驗(yàn)證碼寬度 */ private int width = 67; /** * 高度 */ private int height = 23; /** * 長(zhǎng)度(幾個(gè)數(shù)字) */ private int length = 4; /** * 過(guò)期時(shí)間 */ private int expireIn = 60; /** * 需要圖形驗(yàn)證碼的 url */ private String url; }
(4)、驗(yàn)證
2、添加過(guò)濾器,進(jìn)行驗(yàn)證碼驗(yàn)證
@Component @Slf4j public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean { /** * 登錄失敗處理器 */ @Autowired private AuthenticationFailureHandler failureHandler; /** * Session 對(duì)象 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); /** * 創(chuàng)建一個(gè)Set 集合 存放 需要驗(yàn)證碼的 urls */ private Set<String> urls = new HashSet<>(); /** * spring的一個(gè)工具類(lèi):用來(lái)判斷 兩字符串 是否匹配 */ private AntPathMatcher pathMatcher = new AntPathMatcher(); @Autowired private SecurityProperties securityProperties; /** * 這個(gè)方法是 InitializingBean 接口下的一個(gè)方法, 在初始化配置完成后 運(yùn)行此方法 */ @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); //將 application 配置中的 url 屬性進(jìn)行 切割 String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getUrl(), ","); //添加到 Set 集合里 urls.addAll(Arrays.asList(configUrls)); //因?yàn)榈卿浾?qǐng)求一定要有驗(yàn)證碼 ,所以直接 add 到set 集合中 urls.add("/authentication/form"); } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { boolean action = false; for (String url:urls){ //如果請(qǐng)求的url 和 配置中的url 相匹配 if (pathMatcher.match(url,httpServletRequest.getRequestURI())){ action = true; } } //攔截請(qǐng)求 if (action){ logger.info("攔截成功"+httpServletRequest.getRequestURI()); //如果是登錄請(qǐng)求 try { validate(new ServletWebRequest(httpServletRequest)); }catch (ValidateCodeException exception){ //返回錯(cuò)誤信息給 失敗處理器 failureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,exception); return; } } filterChain.doFilter(httpServletRequest,httpServletResponse); } private void validate(ServletWebRequest request) throws ServletRequestBindingException { //從session中取出 驗(yàn)證碼 ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY); //從request 請(qǐng)求中 取出 驗(yàn)證碼 String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode"); if (StringUtils.isBlank(codeInRequest)){ logger.info("驗(yàn)證碼不能為空"); throw new ValidateCodeException("驗(yàn)證碼不能為空"); } if (codeInSession == null){ logger.info("驗(yàn)證碼不存在"); throw new ValidateCodeException("驗(yàn)證碼不存在"); } if (codeInSession.isExpried()){ logger.info("驗(yàn)證碼已過(guò)期"); sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY); throw new ValidateCodeException("驗(yàn)證碼已過(guò)期"); } if (!StringUtils.equals(codeInSession.getCode(),codeInRequest)){ logger.info("驗(yàn)證碼不匹配"+"codeInSession:"+codeInSession.getCode() +", codeInRequest:"+codeInRequest); throw new ValidateCodeException("驗(yàn)證碼不匹配"); } //把對(duì)應(yīng) 的 session信息 刪掉 sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY); }
3、在核心配置BrowserSecurityConfig中添加過(guò)濾器配置
@Autowired private ValidateCodeFilter validateCodeFilter; @Override protected void configure(HttpSecurity http) throws Exception { //在UsernamePasswordAuthenticationFilter 過(guò)濾器前 加一個(gè)過(guò)濾器 來(lái)搞驗(yàn)證碼 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) //表單登錄 方式 .formLogin() .loginPage("/authentication/require") //登錄需要經(jīng)過(guò)的url請(qǐng)求 .loginProcessingUrl("/authentication/form") .passwordParameter("pwd") .usernameParameter("user") .successHandler(mySuccessHandler) .failureHandler(myFailHandler) .and() //請(qǐng)求授權(quán) .authorizeRequests() //不需要權(quán)限認(rèn)證的url .antMatchers("/authentication/*","/code/image").permitAll() //任何請(qǐng)求 .anyRequest() //需要身份認(rèn)證 .authenticated() .and() //關(guān)閉跨站請(qǐng)求防護(hù) .csrf().disable(); //默認(rèn)注銷(xiāo)地址:/logout http.logout(). //注銷(xiāo)之后 跳轉(zhuǎn)的頁(yè)面 logoutSuccessUrl("/authentication/require"); }
4、異常輔助類(lèi)
public class ValidateCodeException extends AuthenticationException { public ValidateCodeException(String msg, Throwable t) { super(msg, t); } public ValidateCodeException(String msg) { super(msg); } }
5、測(cè)試
(1)、不輸入驗(yàn)證碼
(2)、添加驗(yàn)證碼
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java中對(duì)AtomicInteger和int值在多線(xiàn)程下遞增操作的測(cè)試
這篇文章主要介紹了Java中對(duì)AtomicInteger和int值在多線(xiàn)程下遞增操作的測(cè)試,本文得出AtomicInteger操作 與 int操作的效率大致相差在50-80倍上下的結(jié)論,需要的朋友可以參考下2014-09-09Java多線(xiàn)程中不同條件下編寫(xiě)生產(chǎn)消費(fèi)者模型方法介紹
這篇文章主要介紹了Java多線(xiàn)程中不同條件下編寫(xiě)生產(chǎn)消費(fèi)者模型方法介紹,介紹了生產(chǎn)消費(fèi)者模型,然后分享了相關(guān)代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Java JDK11基于嵌套的訪(fǎng)問(wèn)控制的實(shí)現(xiàn)
這篇文章主要介紹了Java JDK11基于嵌套的訪(fǎng)問(wèn)控制的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01maven私有鏡像倉(cāng)庫(kù)nexus部署使用
Nexus在企業(yè)開(kāi)發(fā)中還是比較常用的私有倉(cāng)庫(kù)管理工具,本文主要介紹了maven私有鏡像倉(cāng)庫(kù)nexus部署使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07深入了解Java核心類(lèi)庫(kù)--泛型類(lèi)
這篇文章主要為大家詳細(xì)介紹了java泛型類(lèi)定義與使用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能給你帶來(lái)幫助2021-07-07Mybatis-Plus接口BaseMapper與Services使用詳解
這篇文章主要為大家介紹了Mybatis-Plus接口BaseMapper與Services使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05基于Java實(shí)現(xiàn)音樂(lè)播放器的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java編寫(xiě)一個(gè)簡(jiǎn)單的音樂(lè)播放器,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-07-07Spring?+?ECharts實(shí)現(xiàn)數(shù)據(jù)可視化的案例詳解
Apache?ECharts是一個(gè)基于?JavaScript?的開(kāi)源可視化圖表庫(kù),在網(wǎng)頁(yè)上實(shí)現(xiàn)數(shù)據(jù)的可視化,非常好用,本文將通過(guò)一個(gè)簡(jiǎn)單的demo來(lái)給大家介紹一下Spring?+?ECharts如何數(shù)據(jù)可視化,需要的朋友可以參考下2023-07-07