龙空技术网

如何基于Spring Security 实现通过短信验证码登录操作?

从程序员到架构师 87

前言:

现在看官们对“java输入用户名和密码 验证码实现登录”大致比较关怀,朋友们都需要知道一些“java输入用户名和密码 验证码实现登录”的相关文章。那么小编同时在网摘上汇集了一些有关“java输入用户名和密码 验证码实现登录””的相关内容,希望小伙伴们能喜欢,同学们快快来了解一下吧!

短信验证码的登录过程通常是用户提供手机号和验证码,服务器验证是否匹配后进行登录。而我们知道在默认情况下Spring Security是基于用户名和密码进行验证的,所以要实现通过短信验证码登录的功能需要我们自定义一个过滤器来处理短信验证码的逻辑。下面我们就来看看具体如何实现短信验证码的登录。

自定义 SmsCodeAuthenticationToken

首先,需要我们定义一个SmsCodeAuthenticationToken,用这个Token来封装手机号码和验证码操作,如下所示。

import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.core.GrantedAuthority;import java.util.Collection;public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {    private final Object principal; // 手机号    private Object credentials;      // 验证码    // 未认证的构造方法    public SmsCodeAuthenticationToken(Object principal, Object credentials) {        super(null);        this.principal = principal;        this.credentials = credentials;        setAuthenticated(false);    }    // 认证成功的构造方法    public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {        super(authorities);        this.principal = principal;        this.credentials = null;        setAuthenticated(true);    }    @Override    public Object getCredentials() {        return credentials;    }    @Override    public Object getPrincipal() {        return principal;    }}
自定义 SmsCodeAuthenticationProvider

接下来,我们需要自定义一个AuthenticationProvider用于验证手机号和验证码的有效性,如下所示。

import org.springframework.security.authentication.AuthenticationProvider;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;import org.springframework.util.StringUtils;public class SmsCodeAuthenticationProvider implements AuthenticationProvider {    private final UserDetailsService userDetailsService;    private final SmsCodeService smsCodeService;  // 验证码处理服务    public SmsCodeAuthenticationProvider(UserDetailsService userDetailsService, SmsCodeService smsCodeService) {        this.userDetailsService = userDetailsService;        this.smsCodeService = smsCodeService;    }    @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        SmsCodeAuthenticationToken token = (SmsCodeAuthenticationToken) authentication;        String phoneNumber = (String) token.getPrincipal();        String smsCode = (String) token.getCredentials();        // 验证验证码        if (!smsCodeService.verifyCode(phoneNumber, smsCode)) {            throw new AuthenticationException("Invalid SMS code") {};        }        // 验证通过后加载用户信息        UserDetails userDetails = userDetailsService.loadUserByUsername(phoneNumber);        // 验证成功,返回认证后的 token        return new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());    }    @Override    public boolean supports(Class<?> authentication) {        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);    }}
编写 SmsCodeFilter

接下来我们需要完成过滤器的编写,来对指定的短信验证码登录请求,进行拦截,判断登录情况。

import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.authentication.AuthenticationServiceException;import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;import org.springframework.security.core.Authentication;import org.springframework.security.core.AuthenticationException;public class SmsCodeFilter extends AbstractAuthenticationProcessingFilter {    public static final String MOBILE_KEY = "mobile";    public static final String SMS_CODE_KEY = "smsCode";    private boolean postOnly = true;    public SmsCodeFilter() {        super("/login/sms"); // 处理 /login/sms 路径的请求    }    @Override    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)            throws AuthenticationException {        if (postOnly && !request.getMethod().equals("POST")) {            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());        }        String mobile = request.getParameter(MOBILE_KEY);        String smsCode = request.getParameter(SMS_CODE_KEY);        if (mobile == null) {            mobile = "";        }        if (smsCode == null) {            smsCode = "";        }        mobile = mobile.trim();        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile, smsCode);        return this.getAuthenticationManager().authenticate(authRequest);    }}
实现短信验证码服务 SmsCodeService

自定义一个短信验证码服务,用于短信验证码的生成以及短信验证码的校验合法性的逻辑处理,如下所示。

import java.util.HashMap;import java.util.Map;public class SmsCodeService {    private Map<String, String> smsCodeStore = new HashMap<>();    // 发送验证码    public void sendSmsCode(String phoneNumber) {        String code = generateCode();        smsCodeStore.put(phoneNumber, code);        // 调用第三方短信平台发送验证码...        System.out.println("发送短信验证码:" + code);    }    // 校验验证码    public boolean verifyCode(String phoneNumber, String code) {        return code.equals(smsCodeStore.get(phoneNumber));    }    private String generateCode() {        return String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 生成6位数字验证码    }}

上面的准备操作完成之后,接下来就是将上面定义的过滤器和 AuthenticationProvider 加入到 Spring Security 的配置类中。如下所示。

Spring Security 配置类

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;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.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    private final UserDetailsService userDetailsService;    private final SmsCodeService smsCodeService;    public SecurityConfig(UserDetailsService userDetailsService, SmsCodeService smsCodeService) {        this.userDetailsService = userDetailsService;        this.smsCodeService = smsCodeService;    }    @Override    protected void configure(HttpSecurity http) throws Exception {        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();        smsCodeFilter.setAuthenticationManager(authenticationManagerBean());        http            .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加自定义过滤器            .authorizeRequests()            .antMatchers("/login/sms").permitAll()            .anyRequest().authenticated()            .and()            .csrf().disable();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.authenticationProvider(smsCodeAuthenticationProvider());    }    @Bean    public SmsCodeAuthenticationProvider smsCodeAuthenticationProvider() {        return new SmsCodeAuthenticationProvider(userDetailsService, smsCodeService);    }}
总结

通过上面的步骤,我们就也可实现一个通过短信验证码进行授权登录的功能。在实际开发过程中,我们可以对上面的操作流程进行一定的调整来符合实际需求情况。

标签: #java输入用户名和密码 验证码实现登录