首页 > 开发 > Java > 正文

SpringBoot + SpringSecurity 短信验证码登录功能实现

2024-07-14 08:41:15
字体:
来源:转载
供稿:网友

实现原理

在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录配置。 但是现在还有一种常见的方式,就是直接通过手机短信验证码登录,这里就需要自己来做一些额外的工作了。

SpringSecurity认证流程详有一定了解的都知道,在帐号密码认证的过程中,涉及到了以下几个类:UsernamePasswordAuthenticationFilter(用于请求参数获取),UsernamePasswordAuthenticationToken(表示用户登录信息),ProviderManager(进行认证校验),

因为是通过的短信验证码登录,所以我们需要对请求的参数,认证过程,用户登录Token信息进行一定的重写。 
当然验证码的过程我们应该放在最前面,如果图形验证的实现一样。这样的做法的好处是:将验证码认证该过程解耦出来,让其他接口也可以使用到。

基本实现

验证码校验

短信验证码的功能实现,其实和图形验证码的原理是一样的。只不过一个是返回给前端一个图片,一个是给用户发送短消息,这里只需要去调用一下短信服务商的接口就好了。更多的原理可以参考 SpringBoot + SpringSecurity 实现图形验证码功能

AuthenticationToken

在使用帐号密码登录的时候,UsernamePasswordAuthenticationToken里面包含了用户的帐号,密码,以及其他的是否可用等状态信息。我们是通过手机短信来做登录,所以就没有密码了,这里我们就直接将UsernamePasswordAuthenticationToken的代码copy过来,把密码相关的信息去掉就可以了

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {  private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;  private final Object principal;  public SmsCodeAuthenticationToken(String mobile) {    super(null);    this.principal = mobile;    setAuthenticated(false);  }  public SmsCodeAuthenticationToken(Object principal,                   Collection<? extends GrantedAuthority> authorities) {    super(authorities);    this.principal = principal;    super.setAuthenticated(true); // must use super, as we override  }  public Object getCredentials() {    return null;  }  public Object getPrincipal() {    return this.principal;  }  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();  }}

AuthenticationFilter

在帐户密码登录的流程中,默认使用的是UsernamePasswordAuthenticationFilter,它的作用是从请求中获取帐户、密码,请求方式校验,生成AuthenticationToken。这里我们的参数是有一定改变的,所以还是老方法,copy过来进行简单的修改

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {  // 请求参数key  private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;  // 是否只支持POST  private boolean postOnly = true;  public SmsCodeAuthenticationFilter() {    // 请求接口的url    super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));  }  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)      throws AuthenticationException {    if (postOnly && !request.getMethod().equals("POST")) {      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());    }    // 根据请求参数名,获取请求value    String mobile = obtainMobile(request);    if (mobile == null) {      mobile = "";    }    mobile = mobile.trim();    // 生成对应的AuthenticationToken    SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);    setDetails(request, authRequest);    return this.getAuthenticationManager().authenticate(authRequest);  }  /**   * 获取手机号   */  protected String obtainMobile(HttpServletRequest request) {    return request.getParameter(mobileParameter);  }  // 省略不相关代码}

Provider

在帐号密码登录的过程中,密码的正确性以及帐号是否可用是通过DaoAuthenticationProvider来校验的。我们也应该自己实现一个Provier

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {  private UserDetailsService userDetailsService;  /**   * 身份逻辑验证   * @param authentication   * @return   * @throws AuthenticationException   */  @Override  public Authentication authenticate(Authentication authentication) throws AuthenticationException {    SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;    UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());    if (user == null) {      throw new InternalAuthenticationServiceException("无法获取用户信息");    }    SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());    authenticationResult.setDetails(authenticationToken.getDetails());    return authenticationResult;  }  @Override  public boolean supports(Class<?> authentication) {    return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);  }  public UserDetailsService getUserDetailsService() {    return userDetailsService;  }  public void setUserDetailsService(UserDetailsService userDetailsService) {    this.userDetailsService = userDetailsService;  }}

配置

主要的认证流程就是通过以上四个过程实现的, 这里我们再降它们配置一下就可以了

@Componentpublic class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {  @Autowired  private AuthenticationSuccessHandler myAuthenticationSuccessHandler;  @Autowired  private AuthenticationFailureHandler myAuthenticationFailureHandler;  @Autowired  private UserDetailsService userDetailsService;  @Override  public void configure(HttpSecurity http) throws Exception {    SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();    smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));    smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);    smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);    SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();    smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);    http.authenticationProvider(smsCodeAuthenticationProvider)        .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);  }} // BrowerSecurityConfig.java@Overrideprotected void configure(HttpSecurity http) throws Exception {  http.apply(smsCodeAuthenticationSecurityConfig);}

代码下载

Spring-Security 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持VeVb武林网。


注:相关教程知识阅读请移步到JAVA教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表