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

作者:whyalwaysmea 时间:2022-10-16 10:26:25 

实现原理

在之前的文章中,我们介绍了普通的帐号密码登录的方式: 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;
 }
}

配置

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


@Component
public 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
@Override
protected void configure(HttpSecurity http) throws Exception {
 http.apply(smsCodeAuthenticationSecurityConfig);
}

代码下载

Spring-Security

来源:https://blog.csdn.net/u013435893/article/details/79684027

标签:SpringBoot,SpringSecurity,短信验证码登录
0
投稿

猜你喜欢

  • Docker学习之基于Dockerfile搭建JAVA Tomcat运行环境的方法

    2022-02-13 17:42:00
  • Java Socket编程详解及示例代码

    2022-06-29 07:05:27
  • Flutter仿网易实现广告卡片3D翻转效果

    2021-07-23 22:38:05
  • Java使用备忘录模式实现过关类游戏功能详解

    2022-11-30 08:52:51
  • Spring Cloud Config对特殊字符加密处理的方法详解

    2021-11-22 15:16:48
  • 利用Android画圆弧canvas.drawArc()实例详解

    2022-05-21 08:45:54
  • 浅谈JavaWeb中的web.xml配置部署描述符文件

    2023-11-12 00:14:13
  • android实现来电静音示例(监听来电)

    2021-09-23 09:43:48
  • java反射之Method的invoke方法实现教程详解

    2023-07-09 20:24:36
  • C#线程同步的几种方法总结

    2022-01-31 19:49:06
  • Android开发手册Chip监听及ChipGroup监听

    2023-03-13 01:16:36
  • android实现横屏的代码及思路

    2023-06-25 09:35:40
  • Java高级特性之反射机制实例详解

    2023-10-08 06:33:51
  • C#实现绘制浮雕图片效果实例

    2023-10-10 04:34:04
  • c# Thread类线程常用操作详解

    2021-09-20 21:35:51
  • Android设计模式之单例模式详解

    2022-03-12 16:51:47
  • 分析设计模式之模板方法Java实现

    2022-01-20 21:02:22
  • C#对XmlHelper帮助类操作Xml文档的通用方法汇总

    2023-06-29 05:37:01
  • Spring Cloud中Sentinel的两种限流模式介绍

    2021-11-22 00:38:10
  • Springmvc ajax跨域请求处理方法实例详解

    2023-08-25 22:56:46
  • asp之家 软件编程 m.aspxhome.com