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)
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值