Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Security技术栈开发企业级认证与授权(十二)将短信验证码验证方式集成到Spring Security

Spring Security技术栈开发企业级认证与授权(十二)将短信验证码验证方式集成到Spring Security

作者头像
itlemon
发布于 2020-04-03 08:24:23
发布于 2020-04-03 08:24:23
89300
代码可运行
举报
文章被收录于专栏:深入理解Java深入理解Java
运行总次数:0
代码可运行

短信登录作为一种常见的登录认证方式,在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的代码,直接将其拷贝过来进行简要修改。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,代码如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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类,将这几个组件集成到一起,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中加入一下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

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

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

完整的配置如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,就可以使用用户名或者手机进行登录,更多详细代码可以从码云中下载查看。大家有什么疑问可以在下方进行留言,我会一一回复。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/01/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
预训练模型助力,真实动画视频生成 | 开源专题 No.92
SadTalker 是一个基于音频驱动的单幅图像对话头像动画生成项目。它可以将单幅人像图像与音频结合,生成逼真的视频对话头像。该项目的主要功能和核心优势包括:
小柒
2024/06/25
1640
预训练模型助力,真实动画视频生成 | 开源专题 No.92
多语言支持 SDK:轻松集成 LLMs 到应用程序 | 开源日报 No.261
semantic-kernel 是一个集成了最新的 LLM 技术(如 OpenAI、Azure OpenAI 和 Hugging Face)与传统编程语言(如 C#、Python 和 Java)的 SDK。该项目主要功能、关键特性和核心优势包括:
小柒
2024/05/17
2210
多语言支持 SDK:轻松集成 LLMs 到应用程序 | 开源日报 No.261
OpenSign:安全可靠的电子签名解决方案 | 开源日报 No.76
这个开源项目是一个为期 12 周的全面课程,由微软云倡导者团队提供。它旨在帮助初学者掌握 JavaScript、CSS 和 HTML 的基础知识。每一节都包括预习和复习测验、详细的书面指南、解决方案、作业等内容。通过以项目为基础的学习方法,该课程旨在帮助您通过实践构建技能,并优化知识积累。
小柒
2023/11/07
8860
OpenSign:安全可靠的电子签名解决方案 | 开源日报 No.76
软件开发自动化到智能文档检索:大语言模型驱动的开源项目盘点 | 开源专题 No.46
这是一个 ChatGPT 代码解释器的开源实现项目,使用了 LangChain 和 CodeBoxes 进行后端沙盒式 Python 代码执行。该项目具有以下特性和优势:
小柒
2023/11/24
4240
软件开发自动化到智能文档检索:大语言模型驱动的开源项目盘点 | 开源专题 No.46
借助 AI 对话文档:100% 私密且无数据泄漏 | 开源日报 No.224
PrivateGPT 是一个可以与您的文档进行交互的项目,利用 GPT 的强大功能,100% 私密且无数据泄漏。
小柒
2024/04/01
1520
借助 AI 对话文档:100% 私密且无数据泄漏 | 开源日报 No.224
实时语音克隆:5 秒内生成任意文本的语音 | 开源日报 No.84
这个开源项目是一个实时语音克隆工具,可以在5秒内复制一种声音,并生成任意文本的语音。
小柒
2023/11/15
4390
实时语音克隆:5 秒内生成任意文本的语音 | 开源日报 No.84
开源日报 0817:以太坊实现、语言模型应用与实用工具
Go Ethereum 是以太坊协议的官方 Golang 执行层实现,可运行各种节点并提供网关访问以太坊网络;LangChain-Chatchat 是基于大语言模型的本地知识库问答应用实现,支持离线运行和多种模型接入;Shiori 是简单易用的书签管理器,支持命令行和 Web 应用程序,且可移植性强;Awesome Generative AI 是精选的生成式人工智能项目和服务列表,用于生成原创内容;DoctorGPT 是功能强大的语言模型,免费离线使用,适用于成为个人医生;Conferences 是会议演讲资料的存储库,方便查找和分享。
小柒
2023/09/02
2380
开源日报 0817:以太坊实现、语言模型应用与实用工具
区块链项目大揭秘,优势各异创未来 | 开源专题 No.79
Namada 是一个基于权益证明的 L1 跨链资产无关隐私平台。它使用 CometBFT 共识,支持任何本地或非本地资产的多资产隐蔽转账,并具有完整的 IBC 协议支持、内置以太坊桥接器、现代化权益证明系统 (自动奖励复利和三次减少)、以及基于股份加权的治理信号机制。并提供了一款多资产保护传输钱包,以便安全且私密地与该协议交互。
小柒
2024/05/17
1900
区块链项目大揭秘,优势各异创未来 | 开源专题 No.79
可无限定制的命令行提示工具:适用任何 Shell 和操作系统 | 开源日报 No.267
Starship 是一个极简、快速且可无限定制的命令行提示工具,可以在任何 shell 和操作系统上使用。其主要功能包括提供高度自定义的提示配置,并以一目了然的方式显示相关信息。
小柒
2024/05/29
1220
可无限定制的命令行提示工具:适用任何 Shell 和操作系统 | 开源日报 No.267
Zulip:开源团队协作工具,高效沟通与远程办公 | 开源日报 No.126
Zulip 是一个开源的团队协作工具,拥有独特的基于主题的线程功能,结合了电子邮件和聊天的优点,使远程工作更加高效和愉快。它是唯一设计用于实时和异步对话的现代团队聊天应用程序。其核心优势包括:
小柒
2023/12/26
4640
Zulip:开源团队协作工具,高效沟通与远程办公 | 开源日报 No.126
完全可定制的富文本编辑器:逻辑清晰,插件赋能 | 开源日报 No.218
Slate 解决了其他富文本库存在的问题,并基于几个原则:插件优先、无固定模式核心逻辑、嵌套文档模型和与 DOM 并行。
小柒
2024/03/19
2130
完全可定制的富文本编辑器:逻辑清晰,插件赋能 | 开源日报 No.218
无头富文本编辑器:框架无关,自由扩展 | 开源日报 No.300
tiptap 是一个面向 Web 开发者的无头富文本编辑器框架,基于可靠的 ProseMirror 库构建。它的核心优势在于:
小柒
2024/07/22
1440
无头富文本编辑器:框架无关,自由扩展 | 开源日报 No.300
Apache 开源现代数据编排平台:低代码易使用、高性能高可用 | 开源日报 No.264
Apache DolphinScheduler 是一款现代数据编排平台,具有低代码高性能工作流的敏捷创建能力。其主要功能和核心优势包括:
小柒
2024/05/29
3280
Apache 开源现代数据编排平台:低代码易使用、高性能高可用 | 开源日报 No.264
开源版 Sora:AI 视频生成的高性能实现 | 开源日报 No.291
Open-Sora 是一个开源项目,提供了类似于 OpenAI 的 Sora 的视频生成模型的高性能实现。该项目的主要功能和核心优势包括:
小柒
2024/07/10
1950
开源版 Sora:AI 视频生成的高性能实现 | 开源日报 No.291
构建、更改和自动化管理基础架构的高效工具 | 开源日报 0831
Terraform 是一个用于安全高效地构建、更改和版本化基础架构的工具。它可以管理现有且流行的服务提供商以及自定义内部解决方案。
小柒
2023/09/02
1950
构建、更改和自动化管理基础架构的高效工具 | 开源日报 0831
GPT-4o 客户端替代方案:支持屏幕阅读、麦克风交互 | 开源日报 No.277
gpt-computer-assistant 是一个为 Windows、macOS 和 Ubuntu 提供的 GPT-4o 替代方案。 该项目旨在为用户提供 ChatGPT MacOS 应用程序的替代品,支持在 Windows 和 Linux 系统上运行。 主要功能和优势包括:
小柒
2024/06/13
2820
GPT-4o 客户端替代方案:支持屏幕阅读、麦克风交互 | 开源日报 No.277
打造你的专属云开发环境:支持任意 IDE,任意云服务 | 开源日报 No.215
devpod 是一个开源的、仅限客户端的、不受限制的工具,可以与任何集成开发环境(IDE)一起使用,并允许您在任何云端、Kubernetes 或本地 Docker 上进行开发。
小柒
2024/03/18
3110
打造你的专属云开发环境:支持任意 IDE,任意云服务 | 开源日报 No.215
Ventoy:打造你的万能启动 U 盘 | 开源日报 No.146
Ventoy 是一个开源工具,用于创建支持 ISO/WIM/IMG/VHD(x)/EFI 文件的可启动 USB 驱动器。其主要功能包括将镜像文件复制到 USB 驱动器并进行引导、一次性复制多个镜像文件并提供引导菜单选择以及在本地磁盘中浏览和引导 ISO/WIM/IMG/VHD(x)/EFI 文件等。该项目的核心优势和关键特点包括:
小柒
2024/01/07
3700
Ventoy:打造你的万能启动 U 盘 | 开源日报 No.146
最佳照片管理应用!AI 赋能,私有部署 | 开源日报 No.211
photoprism 是一个基于人工智能的照片应用程序,适用于去中心化网络。它利用最新技术自动标记和查找图片,无需干扰用户。该项目具有以下主要功能和优势:
小柒
2024/03/18
4110
最佳照片管理应用!AI 赋能,私有部署 | 开源日报 No.211
Diagrams 图表即代码:用代码绘制云系统架构 | 开源日报 0910
Diagrams 是一个以代码形式绘制云系统架构的工具,贯穿“图表即代码”的理念。它可以在没有任何设计工具的情况下对新系统架构设计进行原型设计,还可以描述或可视化现有的系统架构。Diagram 支持主要提供商包括:AWS、Azure、GCP、Kubernetes 等,并且还支持 On-Premise 节点、SaaS 和主要编程框架和语言。Diagram as Code 允许您在任何版本控制系统中跟踪体系结构图变更。
小柒
2023/09/14
3170
Diagrams 图表即代码:用代码绘制云系统架构 | 开源日报 0910
推荐阅读
预训练模型助力,真实动画视频生成 | 开源专题 No.92
1640
多语言支持 SDK:轻松集成 LLMs 到应用程序 | 开源日报 No.261
2210
OpenSign:安全可靠的电子签名解决方案 | 开源日报 No.76
8860
软件开发自动化到智能文档检索:大语言模型驱动的开源项目盘点 | 开源专题 No.46
4240
借助 AI 对话文档:100% 私密且无数据泄漏 | 开源日报 No.224
1520
实时语音克隆:5 秒内生成任意文本的语音 | 开源日报 No.84
4390
开源日报 0817:以太坊实现、语言模型应用与实用工具
2380
区块链项目大揭秘,优势各异创未来 | 开源专题 No.79
1900
可无限定制的命令行提示工具:适用任何 Shell 和操作系统 | 开源日报 No.267
1220
Zulip:开源团队协作工具,高效沟通与远程办公 | 开源日报 No.126
4640
完全可定制的富文本编辑器:逻辑清晰,插件赋能 | 开源日报 No.218
2130
无头富文本编辑器:框架无关,自由扩展 | 开源日报 No.300
1440
Apache 开源现代数据编排平台:低代码易使用、高性能高可用 | 开源日报 No.264
3280
开源版 Sora:AI 视频生成的高性能实现 | 开源日报 No.291
1950
构建、更改和自动化管理基础架构的高效工具 | 开源日报 0831
1950
GPT-4o 客户端替代方案:支持屏幕阅读、麦克风交互 | 开源日报 No.277
2820
打造你的专属云开发环境:支持任意 IDE,任意云服务 | 开源日报 No.215
3110
Ventoy:打造你的万能启动 U 盘 | 开源日报 No.146
3700
最佳照片管理应用!AI 赋能,私有部署 | 开源日报 No.211
4110
Diagrams 图表即代码:用代码绘制云系统架构 | 开源日报 0910
3170
相关推荐
预训练模型助力,真实动画视频生成 | 开源专题 No.92
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验