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)登錄注銷功能的基礎(chǔ)上進(jìn)行開發(fā)。
1、添加生成驗(yàn)證碼的控制器。
(1)、生成驗(yàn)證碼
/**
* 引入 Security 配置屬性類
*/
@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);
//寫給response 響應(yīng)
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
}
(3)、其它輔助類
@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)判斷 兩字符串 是否匹配
*/
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)注銷地址:/logout
http.logout().
//注銷之后 跳轉(zhuǎn)的頁(yè)面
logoutSuccessUrl("/authentication/require");
}
4、異常輔助類
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值在多線程下遞增操作的測(cè)試
這篇文章主要介紹了Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測(cè)試,本文得出AtomicInteger操作 與 int操作的效率大致相差在50-80倍上下的結(jié)論,需要的朋友可以參考下2014-09-09
Java多線程中不同條件下編寫生產(chǎn)消費(fèi)者模型方法介紹
這篇文章主要介紹了Java多線程中不同條件下編寫生產(chǎn)消費(fèi)者模型方法介紹,介紹了生產(chǎn)消費(fèi)者模型,然后分享了相關(guān)代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
Java JDK11基于嵌套的訪問控制的實(shí)現(xiàn)
這篇文章主要介紹了Java JDK11基于嵌套的訪問控制的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
maven私有鏡像倉(cāng)庫(kù)nexus部署使用
Nexus在企業(yè)開發(fā)中還是比較常用的私有倉(cāng)庫(kù)管理工具,本文主要介紹了maven私有鏡像倉(cāng)庫(kù)nexus部署使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
Mybatis-Plus接口BaseMapper與Services使用詳解
這篇文章主要為大家介紹了Mybatis-Plus接口BaseMapper與Services使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Spring?+?ECharts實(shí)現(xiàn)數(shù)據(jù)可視化的案例詳解
Apache?ECharts是一個(gè)基于?JavaScript?的開源可視化圖表庫(kù),在網(wǎng)頁(yè)上實(shí)現(xiàn)數(shù)據(jù)的可視化,非常好用,本文將通過(guò)一個(gè)簡(jiǎn)單的demo來(lái)給大家介紹一下Spring?+?ECharts如何數(shù)據(jù)可視化,需要的朋友可以參考下2023-07-07

