Spring Security技術棧學習筆記(十二)將短信驗證碼驗證方式集成到Spring Security

短信登錄作爲一種常見的登錄認證方式,在Spring Security中是沒有的,本篇博客將繼續在之前文章的基礎上,建立一套短信登錄驗證機制,並將其集成到Spring Security中。

一、短信登錄驗證機制原理分析

Spring Security中,我們最常用的登錄驗證機制是基於用戶名和密碼的,輸入了用戶名和密碼以及圖片驗證碼之後,就會進入一系列的過濾器鏈中,直到驗證成功或者驗證失敗爲止。結合下面的圖,我們來簡要分析一下Spring Security是如何驗證基於用戶名和密碼登錄方式的,分析完畢之後,再一起思考如何將短信登錄驗證方式集成到Spring Security中。

基於用戶名和密碼的認證流程分析
  • 第一步:在登錄頁面輸入了用戶名、密碼和圖片驗證碼,點擊登錄,那麼這個登錄請求首先被圖片驗證碼的驗證過濾器ValidateCodeFilter攔截,因爲我們在BrowserSecurityConfig類中將攔截器ValidateCodeFilter配置到了UsernamePasswordAuthenticationFilter之前,如果驗證碼驗證通過之後,請求將繼續被UsernamePasswordAuthenticationFilter攔截,進入到它的attemptAuthentication方法中進行校驗,該方法首先對請求方法進行了校驗,默認是POST方法,然後從請求中獲取用戶名和密碼,並基於用戶名和密碼生成了UsernamePasswordAuthenticationToken,並在生成Token的過程中將是否認證通過設置爲false
  • 第二步:UsernamePasswordAuthenticationFilterattemptAuthentication方法繼續調用AuthenticationManagerauthenticate方法進行驗證,在真正驗證之前,驗證器會從集合循環遍歷AuthenticationProvider,使用AuthenticationProvider中的supports方法來判斷當前傳遞過來的Token到底應該由哪個AuthenticationProvider來進行校驗,UsernamePasswordAuthenticationToken將由DaoAuthenticationProvider來進行校驗,通過調用UserDetailServiceloadUserByUsername方法來完成驗證,最後結合驗證結果,重新構建UsernamePasswordAuthenticationToken,並將Token中是否認證通過設置爲true完成認證步驟。
基於短信的認證流程分析

分析完基於用戶名和密碼的認證流程之後,我們可以將整個流程應用到需要我們自己定義短信認證的流程中,也就是說,短信認證的流程完全可以參考基於用戶名和密碼的認證流程,那麼我們的短信認證過程也需要有相應的SmsAuthenticationTokenSmsAuthenticationFilterSmsAuthenticationProvider以及驗證短信驗證碼的SmsCodeFilter

  • 第一步:類似於圖片驗證碼的校驗,SmsCodeFilter也需要加到SmsAuthenticationFilter之前,在短信驗證碼驗證通過之後,那麼登錄請求到達SmsAuthenticationFilter,進入到它的attemptAuthentication方法中進行校驗,該方法首先對請求方法進行了校驗,默認是POST方法,然後從請求中獲取到手機號碼,並基於手機號碼來生成SmsAuthenticationToken,並在生成Token的過程中將是否認證通過設置爲false
  • 第二步:SmsAuthenticationFilterattemptAuthentication方法繼續調用AuthenticationManagerauthenticate方法進行驗證,在真正驗證之前,驗證器會從集合循環遍歷AuthenticationProvider,使用AuthenticationProvider中的supports方法來判斷當前傳遞過來的Token到底應該由哪個AuthenticationProvider來進行校驗,SmsAuthenticationToken將由SmsAuthenticationProvider來進行校驗,通過調用UserDetailServiceloadUserByUsername方法來完成驗證,最後結合驗證結果,重新構建UsernamePasswordAuthenticationToken,並將Token中是否認證通過設置爲true完成認證步驟。

分析的過程和基於用戶名和密碼的方式是一模一樣的,那麼我們該如何來寫這四個類的代碼呢?我們當然是要參考對應的代碼了!首先我們先一起寫SmsAuthenticationToken的代碼,那麼我們來參考一下UsernamePasswordAuthenticationToken的代碼,直接將其拷貝過來進行簡要修改。

package com.lemon.security.core.authentication.mobile;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * 使用短信驗證碼登錄的Token,寫法類似{@link UsernamePasswordAuthenticationToken}
 *
 * @author lemon
 * @date 2018/5/7 下午8:38
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // ~ Instance fields
    // ================================================================================================

    private final Object principal;

    // ~ Constructors
    // ===================================================================================================

    /**
     * This constructor can be safely used by any code that wishes to create a
     * <code>SmsCodeAuthenticationToken</code>, as the {@link #isAuthenticated()}
     * will return <code>false</code>.
     *
     * @param mobile 手機號
     */
    public SmsCodeAuthenticationToken(String mobile) {
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }

    /**
     * This constructor should only be used by <code>AuthenticationManager</code> or
     * <code>AuthenticationProvider</code> implementations that are satisfied with
     * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
     * authentication token.
     *
     * @param principal   認證信息
     * @param authorities 權限列表
     */
    public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        // must use super, as we override
        super.setAuthenticated(true);
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

第一個構造方法用於在短信認證前使用,將手機號碼傳入到構造方法中,並將Authenticated設置爲false,第二個構造方法用在認證成功之後,重新構造Token的時候,將手機號和權限列表傳入到其中,並設置Authenticatedtrue
我們繼續編寫SmsAuthenticationFilter的代碼,當然,也是得參考UsernamePasswordAuthenticationFilter,代碼如下所示。

package com.lemon.security.core.authentication.mobile;

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author lemon
 * @date 2018/5/7 下午8:54
 */
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    // ~ Static fields/initializers
    // =====================================================================================

    private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    private static final String SPRING_SECURITY_FORM_SUBMIT_METHOD = "POST";

    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    private boolean postOnly = true;

    // ~ Constructors
    // ===================================================================================================

    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !SPRING_SECURITY_FORM_SUBMIT_METHOD.equals(request.getMethod())) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String mobile = obtainMobile(request);

        if (mobile == null) {
            mobile = "";
        }

        mobile = mobile.trim();

        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 從請求中獲取手機號的方法
     *
     * @param request 請求
     * @return 手機號
     */
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication
     * request's details property.
     *
     * @param request     that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     *                    set
     */
    protected void setDetails(HttpServletRequest request,
                              SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * Sets the parameter name which will be used to obtain the mobile from the login
     * request.
     *
     * @param mobileParameter the parameter name. Defaults to "mobile".
     */
    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }

    /**
     * Defines whether only HTTP POST requests will be allowed by this filter. If set to
     * true, and an authentication request is received which is not a POST request, an
     * exception will be raised immediately and authentication will not be attempted. The
     * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
     * authentication.
     * <p>
     * Defaults to <tt>true</tt> but may be overridden by subclasses.
     */
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMobileParameter() {
        return mobileParameter;
    }
}

寫完SmsCodeAuthenticationFilter,我們繼續編寫SmsCodeAuthenticationProvider,那麼這個Provider也可以繼續模仿DaoAuthenticationProvider來進行編寫,也可以自己實現一套校驗邏輯,主要是調用UserDetailServiceloadUserByUsername方法來進行校驗,只是要注意的是,校驗完畢後,需要重新構建SmsCodeAuthenticationToken,將權限列表傳入到SmsCodeAuthenticationToken中,也就是使用SmsCodeAuthenticationToken的第二個構造方法,構造方法中將Authenticated設置成爲true,最後設置用戶信息細節,並返回該認證後的Token,代碼如下:

package com.lemon.security.core.authentication.mobile;

import lombok.Data;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * 短信登錄的校驗邏輯類
 *
 * @author lemon
 * @date 2018/5/7 下午9:16
 */
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 將Authentication的對象強轉爲SmsCodeAuthenticationToken對象
        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
        // 根據手機號載入用戶信息
        UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
        if (user == null) {
            throw new InternalAuthenticationServiceException("用戶信息不存在");
        }
        // 將獲取到的用戶信息封裝到SmsCodeAuthenticationToken第二個構造方法中,在這個方法中設置爲"已認證"
        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
        // 將用戶的細節信息封裝到已認證的token中
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

前面三個主要的類代碼寫完後,我們繼續寫一個校驗短信驗證碼的類SmsCodeFilter,代碼如下:

package com.lemon.security.core.validate.code;

import com.lemon.security.core.properties.SecurityProperties;
import com.lemon.security.core.validate.code.sms.SmsCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
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;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @author lemon
 * @date 2018/4/6 下午8:23
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean {

    private static final String SUBMIT_FORM_DATA_PATH = "/authentication/mobile";
    private static final String SMS_SESSION_KEY = ValidateCodeProcessor.SESSION_KEY_PREFIX + "SMS";

    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    private Set<String> urls = new HashSet<>();

    private SecurityProperties securityProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getSms().getUrl(), ",");
        urls.addAll(Arrays.asList(configUrls));
        // 登錄的鏈接是必須要進行驗證碼驗證的
        urls.add(SUBMIT_FORM_DATA_PATH);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        boolean action = false;
        for (String url : urls) {
            // 如果實際訪問的URL可以與用戶在YML配置文件中配置的相同,那麼就進行驗證碼校驗
            if (antPathMatcher.match(url, request.getRequestURI())) {
                action = true;
            }
        }
        if (action) {
            try {
                validate(new ServletWebRequest(request));
            } catch (ValidateCodeException e) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 驗證碼校驗邏輯
     *
     * @param request 請求
     * @throws ServletRequestBindingException 請求異常
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        // 從session中獲取短信驗證碼
        SmsCode smsCodeInSession = (SmsCode) sessionStrategy.getAttribute(request, SMS_SESSION_KEY);
        // 從請求中獲取用戶填寫的驗證碼
        String smsCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "smsCode");
        if (StringUtils.isBlank(smsCodeInRequest)) {
            throw new ValidateCodeException("驗證碼不能爲空");
        }
        if (null == smsCodeInSession) {
            throw new ValidateCodeException("驗證碼不存在");
        }
        if (smsCodeInSession.isExpired()) {
            sessionStrategy.removeAttribute(request, SMS_SESSION_KEY);
            throw new ValidateCodeException("驗證碼已過期");
        }
        if (!StringUtils.equalsIgnoreCase(smsCodeInRequest, smsCodeInSession.getCode())) {
            throw new ValidateCodeException("驗證碼不匹配");
        }
        // 驗證成功,刪除session中的驗證碼
        sessionStrategy.removeAttribute(request, SMS_SESSION_KEY);
    }
}

我們將這四個類寫好了,那麼如何按照上面圖片顯示的那樣,將其納入到Spring Security的管理之中呢,我們在第二小節下分析。

二、將短信登錄驗證機制集成到Spring Security中

我們需要將各個組件集成到Spring Security中,那麼就需要有一個配置類來完成配置,我們來編寫一個SmsCodeAuthenticationSecurityConfig類,將這幾個組件集成到一起,代碼如下:

package com.lemon.security.core.authentication.mobile;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
 * 短信驗證的配置類
 *
 * @author jiangpingping
 * @date 2019-01-31 22:17
 */
@Component("smsCodeAuthenticationSecurityConfig")
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final AuthenticationSuccessHandler lemonAuthenticationSuccessHandler;

    private final AuthenticationFailureHandler lemonAuthenticationFailureHandler;

    private final UserDetailsService userDetailsService;

    @Autowired
    public SmsCodeAuthenticationSecurityConfig(AuthenticationSuccessHandler lemonAuthenticationSuccessHandler,
                                               AuthenticationFailureHandler lemonAuthenticationFailureHandler,
                                               UserDetailsService userDetailsService) {
        this.lemonAuthenticationSuccessHandler = lemonAuthenticationSuccessHandler;
        this.lemonAuthenticationFailureHandler = lemonAuthenticationFailureHandler;
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 首先配置Sms驗證的過濾器,在其中配置AuthenticationManager,驗證成功處理器、失敗處理器和認證的Provider等信息
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(lemonAuthenticationSuccessHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(lemonAuthenticationFailureHandler);

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);

        // 將Provider註冊到Spring Security中,將Filter加到UsernamePasswordAuthenticationFilter後面
        http.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

在上面的配置方法configure中,我們首先創建一個SmsCodeAuthenticationFilter實例對象,然後設置了一個通用的AuthenticationManager,還設置了驗證成功處理器和失敗處理器,然後又創建了一個SmsCodeAuthenticationProvider實例對象,並將UserDetailService實現類對象設置到了其中,並將SmsCodeAuthenticationFilter過濾器加到了UsernamePasswordAuthenticationFilter的後面。這樣做還是不夠的,我們還需要在類BrowserSecurityConfig中加入一下代碼:

@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
smsCodeFilter.setAuthenticationFailureHandler(lemonAuthenticationFailureHandler);
smsCodeFilter.setSecurityProperties(securityProperties);
smsCodeFilter.afterPropertiesSet();

http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.apply(smsCodeAuthenticationSecurityConfig);

完整的配置如下所示:

package com.lemon.security.browser;

import com.lemon.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.lemon.security.core.properties.SecurityProperties;
import com.lemon.security.core.validate.code.SmsCodeFilter;
import com.lemon.security.core.validate.code.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * 瀏覽器安全驗證的配置類
 *
 * @author lemon
 * @date 2018/4/3 下午7:35
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    private final SecurityProperties securityProperties;
    private final AuthenticationSuccessHandler lemonAuthenticationSuccessHandler;
    private final AuthenticationFailureHandler lemonAuthenticationFailureHandler;
    private final DataSource dataSource;

    @Autowired
    public BrowserSecurityConfig(SecurityProperties securityProperties, AuthenticationSuccessHandler lemonAuthenticationSuccessHandler, AuthenticationFailureHandler lemonAuthenticationFailureHandler, DataSource dataSource) {
        this.securityProperties = securityProperties;
        this.lemonAuthenticationSuccessHandler = lemonAuthenticationSuccessHandler;
        this.lemonAuthenticationFailureHandler = lemonAuthenticationFailureHandler;
        this.dataSource = dataSource;
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    /**
     * 配置了這個Bean以後,從前端傳遞過來的密碼將被加密
     *
     * @return PasswordEncoder實現類對象
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PersistentTokenRepository tokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(lemonAuthenticationFailureHandler);
        validateCodeFilter.setSecurityProperties(securityProperties);
        validateCodeFilter.afterPropertiesSet();

        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
        smsCodeFilter.setAuthenticationFailureHandler(lemonAuthenticationFailureHandler);
        smsCodeFilter.setSecurityProperties(securityProperties);
        smsCodeFilter.afterPropertiesSet();

        http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
                .successHandler(lemonAuthenticationSuccessHandler)
                .failureHandler(lemonAuthenticationFailureHandler)
                .and()
                .rememberMe()
                .tokenRepository(tokenRepository())
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                .userDetailsService(userDetailsService)
                .and()
                .authorizeRequests()
                .antMatchers("/authentication/require", securityProperties.getBrowser().getLoginPage(), "/code/*").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable()
                .apply(smsCodeAuthenticationSecurityConfig);
    }
}

直到這裏,我們算是完成短信驗證碼的登錄方式,並將其集成到了Spring Security中,我們訪問項目的默認登錄頁面http://localhost:8080/login.html,就可以使用用戶名或者手機進行登錄,更多詳細代碼可以從碼雲中下載查看。大家有什麼疑問可以在下方進行留言,我會一一回復。

Spring Security技術棧開發企業級認證與授權系列文章列表:

Spring Security技術棧學習筆記(一)環境搭建
Spring Security技術棧學習筆記(二)RESTful API詳解
Spring Security技術棧學習筆記(三)表單校驗以及自定義校驗註解開發
Spring Security技術棧學習筆記(四)RESTful API服務異常處理
Spring Security技術棧學習筆記(五)使用Filter、Interceptor和AOP攔截REST服務
Spring Security技術棧學習筆記(六)使用REST方式處理文件服務
Spring Security技術棧學習筆記(七)使用Swagger自動生成API文檔
Spring Security技術棧學習筆記(八)Spring Security的基本運行原理與個性化登錄實現
Spring Security技術棧學習筆記(九)開發圖形驗證碼接口
Spring Security技術棧學習筆記(十)開發記住我功能
Spring Security技術棧學習筆記(十一)開發短信驗證碼登錄
Spring Security技術棧學習筆記(十二)將短信驗證碼驗證方式集成到Spring Security
Spring Security技術棧學習筆記(十三)Spring Social集成第三方登錄驗證開發流程介紹
Spring Security技術棧學習筆記(十四)使用Spring Social集成QQ登錄驗證方式
Spring Security技術棧學習筆記(十五)解決Spring Social集成QQ登錄後的註冊問題
Spring Security技術棧學習筆記(十六)使用Spring Social集成微信登錄驗證方式

示例代碼下載地址:

項目已經上傳到碼雲,歡迎下載,內容所在文件夾爲chapter012

更多幹貨分享,歡迎關注我的微信公衆號:爪哇論劍(微信號:itlemon)
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章