Springboot+SpringSecurity實現圖片驗證碼登錄問題


這個問題,網上找了好多,結果代碼都不全,找了好多,要不是就自動注入的類注入不了,編譯報錯,要不異常捕獲不了浪費好多時間,就覺得,框架不熟就不能隨便用,全是坑,氣死我了,最后改了兩天.終於弄好啦;

問題主要是:

  1. 返回的驗證碼不知道在SpringSecurity的什么地方和存在內存里的比較?我用的方法是前置一個過濾器,插入到表單驗證之前。
  2. 比較之后應該怎么處理,:比較之后要拋出一個繼承了AuthenticationException的異常
  3. 其次是捕獲驗證碼錯誤異常的處理? 捕獲到的異常交給自定義驗證失敗處理器AuthenticationFailureHandler處理,這里,用的處理器要和表單驗證失敗的處理器是同一個處理器,不然會報異常,所以在需要寫一個全局的AuthenticationFailureHandler的實現類,專門用來處理異常。表單驗證有成功和失敗兩個處理器,我們一般直接以匿名內部類形似寫。要是加了驗證碼,就必須使用統一的失敗處理器。

 

效果圖:

 

 

網上大都是直接注入一個AuthenticationFailureHandler,我當時就不明白這個咋注進去的,我這個一寫就報錯,注入不進去,后來就想自己new一個哇,可以是可以了,但是還報異常,java.lang.IllegalStateException: Cannot call sendError() after the response has been committed,后來想表單驗證的時候,失敗用的也是這個處理器,就想定義一個全局的處理器,

 

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());
           // 驗證碼自定義異常的處理
        if (e instanceof ValidateCodeException){
            respBean.setMsg(e.getMessage());
            //Security內置的異常處理
        }else if (e instanceof LockedException) {
            respBean.setMsg("賬戶被鎖定請聯系管理員!");
        } else if (e instanceof CredentialsExpiredException) {
            respBean.setMsg("密碼過期請聯系管理員!");
        } else if (e instanceof AccountExpiredException) {
            respBean.setMsg("賬戶過期請聯系管理員!");
        } else if (e instanceof DisabledException) {
            respBean.setMsg("賬戶被禁用請聯系管理員!");
        } else if (e instanceof BadCredentialsException) {
            respBean.setMsg("用戶名密碼輸入錯誤,請重新輸入!");
        }
        //將hr轉化為Sting
        out.write(new ObjectMapper().writeValueAsString(respBean));
        out.flush();
        out.close();
    }
    @Bean
    public  MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){
        return new MyAuthenticationFailureHandler();
    }
}

 

 

 

流程 

  1.  請求登錄頁,將驗證碼結果存到基於Servlet的session里,以JSON格式返回驗證碼,

  2. 之后前端發送登錄請求,SpringSecurity中處理,自定義一個filter讓它繼承自OncePerRequestFilter,然后重寫doFilterInternal方法,在這個方法中實現驗證碼驗證的功能,如果驗證碼錯誤就拋出一個繼承自AuthenticationException的驗證嗎錯誤的異常消息寫入到響應消息中.

  3. 之后返回異常信息交給自定義驗證失敗處理器處理。

下面以這個順序書寫代碼:

依賴大家照着import導一下吧,記得有這兩個,驗證碼需要一個依賴,之后還使用了一個工具包

<!--圖片驗證-->
        <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>

前端使用Vue+ElementUI

     
        <div class="login-code">
          <img :src="codeUrl"
               @click="getCode">
        </div>
 
     

后端代碼:

獲取驗證碼,將結果放到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){
        // 算術類型 https://gitee.com/whvse/EasyCaptcha
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
        // 幾位數運算,默認是兩位
        captcha.setLen(2);
        // 獲取運算的結果
        String result = captcha.text();
        System.err.println("生成的驗證碼:"+result);
        // 保存
        // 驗證碼信息
        Map<String,Object> imgResult = new HashMap<String,Object>(2){{
            put("img", captcha.toBase64());
    
        }};
        request.getSession().setAttribute("yanzhengma",result);

        return imgResult;
    }
}

定義一個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. 進行驗證碼的校驗
            try {
            String requestCaptcha = request.getParameter("code");
                if (requestCaptcha == null) {
                    throw new ValidateCodeException("驗證碼不存在");
                }
            String code = (String) request.getSession().getAttribute("yanzhengma");
                if (StringUtils.isBlank(code)) {
                    throw new ValidateCodeException("驗證碼過期!");
                }
            code = code.equals("0.0") ? "0" : code;
            logger.info("開始校驗驗證碼,生成的驗證碼為:" + code + " ,輸入的驗證碼為:" + requestCaptcha);
                if (!StringUtils.equals(code, requestCaptcha)) {
                    throw new ValidateCodeException("驗證碼不匹配");
                }
            } catch (AuthenticationException e) {
                // 2. 捕獲步驟1中校驗出現異常,交給失敗處理類進行進行處理
                myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
            } finally {
                filterChain.doFilter(request, response);
            }
        } else {
            filterChain.doFilter(request, response);
        }


    }

}

 

定義一個自定義異常處理,繼承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配置. 

在之前的基礎上加filter的基礎上加了

http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class),驗證處理上,驗證碼和表單驗證失敗用同一個失敗處理器,

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  放行的請求路徑
     * @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") //登錄成功回調 .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轉化為Sting String s = new ObjectMapper().writeValueAsString(ok); out.write(s); out.flush(); out.close(); } }) //登失敗回調 .failureHandler(myAuthenticationFailureHandler) //相關的接口直接返回 .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() //沒有認證時,在這里處理結果,不要重定向 .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("請求失敗,請聯系管理員!"); } out.write(new ObjectMapper().writeValueAsString(respBean)); out.flush(); out.close(); } }); } }

--------------------------------------------------------------------------------------

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM