SpringBoot+SpringSecurity登陸認證優雅集成圖形驗證碼

前言

在SpringSecurity的默認登陸支持組件formLogin中沒有提供圖形驗證碼的支持,目前大多數的方案都是經過新增Filter來實現。filter的方式能夠實現功能,可是沒有優雅的解決, 須要從新維護一套和登陸相關的url,例如:loginProccessUrl,loginFailUrl,loginSuccessUrl,從軟件設計角度來說功能沒有內聚。
下面爲你們介紹一種優雅的解決方案。html

解決思路

先獲取驗證碼

判斷圖形驗證碼先要獲取到驗證碼,在UsernamePasswordAuthenticationToken(UPAT)中沒有字段來存儲驗證碼,重寫UPAT成本過高。能夠從details字段中入手,將驗證碼放在details中。git

判斷驗證碼是否正確

UPAT的認證是在DaoAuthenticationProvider中完成的,若是須要判斷驗證碼直接修改是成本比較大的方式,能夠新增AuthenticationProvider來對驗證碼新增驗證。web

輸出驗證碼

常規超過能夠經過Controller來輸出,可是驗證碼的管理須要統一,防止各類sessionKey亂飛。spring

代碼實現

新增驗證碼容器:CaptchaAuthenticationDetails

public class CaptchaAuthenticationDetails extends WebAuthenticationDetails {
    private final String DEFAULT_CAPTCHA_PARAMETER_NAME = "captcha";
 private String captchaParameter = DEFAULT_CAPTCHA_PARAMETER_NAME;
/**
 * 用戶提交的驗證碼
 */
private String committedCaptcha;
/**
 * 預設的驗證碼
 */
private String presetCaptcha;
 private final WebAuthenticationDetails webAuthenticationDetails;
 public CaptchaAuthenticationDetails(HttpServletRequest request) {
        super(request);
 this.committedCaptcha = request.getParameter(captchaParameter);
 this.webAuthenticationDetails = new WebAuthenticationDetails(request);
 }
    public boolean isCaptchaMatch() {
        if (this.presetCaptcha == null || this.committedCaptcha == null) {
            return false;
 }
        return this.presetCaptcha.equalsIgnoreCase(committedCaptcha);
 }
 getter ...
 setter ...
 }

這個類主要是用於保存驗證碼session

驗證碼獲取:CaptchaAuthenticationDetailsSource

public class CaptchaAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, CaptchaAuthenticationDetails> {
    private final CaptchaRepository<HttpServletRequest> captchaRepository;
 public CaptchaAuthenticationDetailsSource(CaptchaRepository<HttpServletRequest> captchaRepository) {
        this.captchaRepository = captchaRepository;
 }
    @Override
 public CaptchaAuthenticationDetails buildDetails(HttpServletRequest httpServletRequest) {
        CaptchaAuthenticationDetails captchaAuthenticationDetails = new CaptchaAuthenticationDetails(httpServletRequest);
 captchaAuthenticationDetails.setPresetCaptcha(captchaRepository.load(httpServletRequest));
 return captchaAuthenticationDetails;
 }
}

根據提交的參數構建CaptchaAuthenticationDetails,用戶提交的驗證碼(committedCaptcha)從request中獲取,預設的驗證碼(presetCaptcha)從驗證碼倉庫(CaptchaRepostory)獲取mvc

驗證碼倉庫實現SessionCaptchaRepository

public class SessionCaptchaRepository implements CaptchaRepository<HttpServletRequest> {
    private static final String CAPTCHA_SESSION_KEY = "captcha";
 /**
 * the key of captcha in session attributes */ private String captchaSessionKey = CAPTCHA_SESSION_KEY;
 @Override
 public String load(HttpServletRequest request) {
        return (String) request.getSession().getAttribute(captchaSessionKey);
 }
    @Override
 public void save(HttpServletRequest request, String captcha) {
        request.getSession().setAttribute(captchaSessionKey, captcha);
 }
    /**
 * @return sessionKey */ public String getCaptchaSessionKey() {
        return captchaSessionKey;
 }
    /**
 * @param captchaSessionKey sessionKey
 */ public void setCaptchaSessionKey(String captchaSessionKey) {
        this.captchaSessionKey = captchaSessionKey;
 }
}

這個驗證碼倉庫是基於Session的,若是想要基於Redis只要實現CaptchaRepository便可。app

對驗證碼進行認證CaptchaAuthenticationProvider

public class CaptchaAuthenticationProvider implements AuthenticationProvider {
    private final Logger log = LoggerFactory.getLogger(this.getClass());
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
 CaptchaAuthenticationDetails details = (CaptchaAuthenticationDetails) authenticationToken.getDetails();
 if (!details.isCaptchaMatch()) {
            //驗證碼不匹配拋出異常,退出認證
 if (log.isDebugEnabled()) {
                log.debug("認證失敗:驗證碼不匹配");
 }
            throw new CaptchaIncorrectException("驗證碼錯誤");
 }
        //替換details
 authenticationToken.setDetails(details.getWebAuthenticationDetails());
 //返回空交給下一個provider進行認證
 return null;
 }
    @Override
 public boolean supports(Class<?> aClass) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
 }
}

SpringSecurity配置

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
 public CaptchaRepository<HttpServletRequest> sessionCaptchaRepository() {
        return new SessionCaptchaRepository();
 }
    @Override
 protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                .authenticationDetailsSource(new CaptchaAuthenticationDetailsSource(sessionCaptchaRepository()))
                .failureUrl("/login.html?error=true")
                .defaultSuccessUrl("/index.html")
                .and()
                .authenticationProvider(new CaptchaAuthenticationProvider())
                .csrf()
                .disable();
 }
    @Override
 public void configure(WebSecurity web) {
        web.ignoring()
                .mvcMatchers("/captcha", "/login.html");
 }
}

將CaptchaAuthenticationProvider加入到認證鏈條中,從新配置authenticationDetailsSourcedom

提供圖形驗證碼接口

@Controller
public class CaptchaController {
    private final CaptchaRepository<HttpServletRequest> captchaRepository;
 @Autowired
 public CaptchaController(CaptchaRepository<HttpServletRequest> captchaRepository) {
        this.captchaRepository = captchaRepository;
 }
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        RandomCaptcha randomCaptcha = new RandomCaptcha(4);
 captchaRepository.save(request, randomCaptcha.getValue());
 CaptchaImage captchaImage = new DefaultCaptchaImage(200, 60, randomCaptcha.getValue());
 captchaImage.write(response.getOutputStream());
 }
}

將生成的隨機驗證碼(RandomCaptcha)保存到驗證碼倉庫(CaptchaRepository)中,並將驗證碼圖片(CaptchaImage)輸出到客戶端。
至此整個圖形驗證碼認證的全流程已經結束。ide

代碼倉庫

https://gitee.com/leecho/spri...post

相關文章
相關標籤/搜索