Springboot+SpringSecurity實(shí)現(xiàn)圖片驗(yàn)證碼登錄的示例
這個(gè)問題,網(wǎng)上找了好多,結(jié)果代碼都不全,找了好多,要不是就自動(dòng)注入的類注入不了,編譯報(bào)錯(cuò),要不異常捕獲不了浪費(fèi)好多時(shí)間,就覺得,框架不熟就不能隨便用,全是坑,氣死我了,最后改了兩天.終于弄好啦;
問題主要是:
- 返回的驗(yàn)證碼不知道在SpringSecurity的什么地方和存在內(nèi)存里的比較?我用的方法是前置一個(gè)過濾器,插入到表單驗(yàn)證之前。
- 比較之后應(yīng)該怎么處理,:比較之后要拋出一個(gè)繼承了AuthenticationException的異常
- 其次是捕獲驗(yàn)證碼錯(cuò)誤異常的處理? 捕獲到的異常交給自定義驗(yàn)證失敗處理器AuthenticationFailureHandler處理,這里,用的處理器要和表單驗(yàn)證失敗的處理器是同一個(gè)處理器,不然會(huì)報(bào)異常,所以在需要寫一個(gè)全局的AuthenticationFailureHandler的實(shí)現(xiàn)類,專門用來處理異常。表單驗(yàn)證有成功和失敗兩個(gè)處理器,我們一般直接以匿名內(nèi)部類形似寫。要是加了驗(yàn)證碼,就必須使用統(tǒng)一的失敗處理器。
效果圖

網(wǎng)上大都是直接注入一個(gè)AuthenticationFailureHandler,我當(dāng)時(shí)就不明白這個(gè)咋注進(jìn)去的,我這個(gè)一寫就報(bào)錯(cuò),注入不進(jìn)去,后來就想自己new一個(gè)哇,可以是可以了,但是還報(bào)異常,java.lang.IllegalStateException: Cannot call sendError() after the response has been committed,后來想表單驗(yàn)證的時(shí)候,失敗用的也是這個(gè)處理器,就想定義一個(gè)全局的處理器,
package com.liruilong.hros.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.model.RespBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
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;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/11 23:08
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
// 驗(yàn)證碼自定義異常的處理
if (e instanceof ValidateCodeException){
respBean.setMsg(e.getMessage());
//Security內(nèi)置的異常處理
}else if (e instanceof LockedException) {
respBean.setMsg("賬戶被鎖定請(qǐng)聯(lián)系管理員!");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMsg("密碼過期請(qǐng)聯(lián)系管理員!");
} else if (e instanceof AccountExpiredException) {
respBean.setMsg("賬戶過期請(qǐng)聯(lián)系管理員!");
} else if (e instanceof DisabledException) {
respBean.setMsg("賬戶被禁用請(qǐng)聯(lián)系管理員!");
} else if (e instanceof BadCredentialsException) {
respBean.setMsg("用戶名密碼輸入錯(cuò)誤,請(qǐng)重新輸入!");
}
//將hr轉(zhuǎn)化為Sting
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
@Bean
public MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){
return new MyAuthenticationFailureHandler();
}
}流程
- 請(qǐng)求登錄頁,將驗(yàn)證碼結(jié)果存到基于Servlet的session里,以JSON格式返回驗(yàn)證碼,
- 之后前端發(fā)送登錄請(qǐng)求,SpringSecurity中處理,自定義一個(gè)filter讓它繼承自O(shè)ncePerRequestFilter,然后重寫doFilterInternal方法,在這個(gè)方法中實(shí)現(xiàn)驗(yàn)證碼驗(yàn)證的功能,如果驗(yàn)證碼錯(cuò)誤就拋出一個(gè)繼承自AuthenticationException的驗(yàn)證嗎錯(cuò)誤的異常消息寫入到響應(yīng)消息中.
- 之后返回異常信息交給自定義驗(yàn)證失敗處理器處理。
下面以這個(gè)順序書寫代碼:
依賴大家照著import導(dǎo)一下吧,記得有這兩個(gè),驗(yàn)證碼需要一個(gè)依賴,之后還使用了一個(gè)工具依賴包,之后是前端代碼
?<!--圖片驗(yàn)證-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency> <div class="login-code">
<img :src="codeUrl"
@click="getCode">
</div>
后端代碼:
獲取驗(yàn)證碼,將結(jié)果放到session里
package com.liruilong.hros.controller;
import com.liruilong.hros.model.RespBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.wf.captcha.ArithmeticCaptcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* @Description :
* @Author: Liruilong
* @Date: 2019/12/19 19:58
*/
@RestController
public class LoginController {
@GetMapping(value = "/auth/code")
public Map getCode(HttpServletRequest request,HttpServletResponse response){
// 算術(shù)類型 https://gitee.com/whvse/EasyCaptcha
ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
// 幾位數(shù)運(yùn)算,默認(rèn)是兩位
captcha.setLen(2);
// 獲取運(yùn)算的結(jié)果
String result = captcha.text();
System.err.println("生成的驗(yàn)證碼:"+result);
// 保存
// 驗(yàn)證碼信息
Map<String,Object> imgResult = new HashMap<String,Object>(2){{
put("img", captcha.toBase64());
}};
request.getSession().setAttribute("yanzhengma",result);
return imgResult;
}
}定義一個(gè)VerifyCodeFilter 過濾器
package com.liruilong.hros.filter;
import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.config.MyAuthenticationFailureHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
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 java.io.IOException;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/7 19:39
*/
@Component
public class VerifyCodeFilter extends OncePerRequestFilter {
@Bean
public VerifyCodeFilter getVerifyCodeFilter() {
return new VerifyCodeFilter();
}
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (StringUtils.equals("/doLogin", request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
// 1. 進(jìn)行驗(yàn)證碼的校驗(yàn)
try {
String requestCaptcha = request.getParameter("code");
if (requestCaptcha == null) {
throw new ValidateCodeException("驗(yàn)證碼不存在");
}
String code = (String) request.getSession().getAttribute("yanzhengma");
if (StringUtils.isBlank(code)) {
throw new ValidateCodeException("驗(yàn)證碼過期!");
}
code = code.equals("0.0") ? "0" : code;
logger.info("開始校驗(yàn)驗(yàn)證碼,生成的驗(yàn)證碼為:" + code + " ,輸入的驗(yàn)證碼為:" + requestCaptcha);
if (!StringUtils.equals(code, requestCaptcha)) {
throw new ValidateCodeException("驗(yàn)證碼不匹配");
}
} catch (AuthenticationException e) {
// 2. 捕獲步驟1中校驗(yàn)出現(xiàn)異常,交給失敗處理類進(jìn)行進(jìn)行處理
myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
} finally {
filterChain.doFilter(request, response);
}
} else {
filterChain.doFilter(request, response);
}
}
}定義一個(gè)自定義異常處理,繼承AuthenticationException
package com.liruilong.hros.Exception;
import org.springframework.security.core.AuthenticationException;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/8 7:24
*/
public class ValidateCodeException extends AuthenticationException {
public ValidateCodeException(String msg) {
super(msg);
}
}security配置.
在之前的基礎(chǔ)上加filter的基礎(chǔ)上加了
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class),驗(yàn)證處理上,驗(yàn)證碼和表單驗(yàn)證失敗用同一個(gè)失敗處理器,
package com.liruilong.hros.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.filter.VerifyCodeFilter;
import com.liruilong.hros.model.Hr;
import com.liruilong.hros.model.RespBean;
import com.liruilong.hros.service.HrService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @Description :
* @Author: Liruilong
* @Date: 2019/12/18 19:11
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Autowired
CustomUrlDecisionManager customUrlDecisionManager;
@Autowired
VerifyCodeFilter verifyCodeFilter ;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(hrService);
}
/**
* @Author Liruilong
* @Description 放行的請(qǐng)求路徑
* @Date 19:25 2020/2/7
* @Param [web]
* @return void
**/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/auth/code","/login","/css/**","/js/**", "/index.html", "/img/**", "/fonts/**","/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
//.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
.and().formLogin().usernameParameter("username").passwordParameter("password") .loginProcessingUrl("/doLogin")
.loginPage("/login")
//登錄成功回調(diào)
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
Hr hr = (Hr) authentication.getPrincipal();
//密碼不回傳
hr.setPassword(null);
RespBean ok = RespBean.ok("登錄成功!", hr);
//將hr轉(zhuǎn)化為Sting
String s = new ObjectMapper().writeValueAsString(ok);
out.write(s);
out.flush();
out.close();
}
})
//登失敗回調(diào)
.failureHandler(myAuthenticationFailureHandler)
//相關(guān)的接口直接返回
.permitAll()
.and()
.logout()
//注銷登錄
// .logoutSuccessUrl("")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注銷成功!")));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf().disable().exceptionHandling()
//沒有認(rèn)證時(shí),在這里處理結(jié)果,不要重定向
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
resp.setStatus(401);
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error("訪問失敗!");
if (authException instanceof InsufficientAuthenticationException) {
respBean.setMsg("請(qǐng)求失敗,請(qǐng)聯(lián)系管理員!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
});
}
}
到此這篇關(guān)于Springboot+SpringSecurity實(shí)現(xiàn)圖片驗(yàn)證碼登錄的示例的文章就介紹到這了,更多相關(guān)Springboot SpringSecurity圖片驗(yàn)證碼登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
教你用IDEA配置JUnit并進(jìn)行單元測(cè)試
今天教各位小伙伴怎么用IDEA配置JUnit并進(jìn)行單元測(cè)試,文中有非常詳細(xì)的圖文介紹及代碼示例,對(duì)正在學(xué)習(xí)IDEA的小伙伴有很好的幫助,需要的朋友可以參考下2021-05-05
java發(fā)送email一般步驟(實(shí)例講解)
下面小編就為大家?guī)硪黄猨ava發(fā)送email一般步驟(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
java實(shí)現(xiàn)微信支付結(jié)果通知
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信支付結(jié)果通知,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
SpringBoot之解決多個(gè)定時(shí)任務(wù)阻塞的問題
這篇文章主要介紹了SpringBoot之解決多個(gè)定時(shí)任務(wù)阻塞的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
詳解MybatisPlus中@TableLogic注解的使用
@TableLogic一般用于實(shí)現(xiàn)數(shù)據(jù)庫數(shù)據(jù)邏輯刪除,本文我們將介紹 @TableLogic 注解的用法,以及每個(gè)屬性的實(shí)際意義和用法,感興趣的可以了解一下2022-06-06
Java經(jīng)典設(shè)計(jì)模式之模板方法模式定義與用法示例
這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之模板方法模式,簡(jiǎn)單說明了模板方法模式的原理、定義,并結(jié)合實(shí)例形式分析了java模板方法模式的具體使用方法,需要的朋友可以參考下2017-08-08
idea中service或者mapper引入報(bào)紅的問題及解決
在使用IntelliJ IDEA開發(fā)SpringBoot項(xiàng)目時(shí),有時(shí)會(huì)遇到Service或Mapper接口引入時(shí)報(bào)紅但不影響項(xiàng)目運(yùn)行的情況,這主要是因?yàn)镮DEA的檢查級(jí)別設(shè)置問題,解決方法是將有問題的Error級(jí)別改為編譯通過的安全級(jí)別,即可消除報(bào)紅2024-09-09

