SpringBoot+Security 发送短信验证码的实现

下面详细讲解 Spring Boot 和 Spring Security 实现发送短信验证码的完整攻略

1. 简介

Spring Boot 是一个快速开发和方便配置的 Java Web 开发框架。它可以帮助开发人员快速构建可部署的、生产级别的、面向互联网的应用程序。

Spring Security 是用于保护 Java Web 应用程序的框架。它可以保护 Web 应用程序的 URL,控制用户访问 Web 页面的权限,防止各种 Web 攻击(如 CSRF、XSS、点击劫持、SQL 注入等)和其他安全问题。

本篇攻略将详细介绍如何结合 Spring Boot 和 Spring Security 实现发送短信验证码。

2. 准备工作

2.1. 添加依赖

pom.xml 文件中添加以下依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
  <version>1.0.0-beta</version>
</dependency>

2.2. 配置 Aliyun SMS

在阿里云控制台开通短信服务,并创建签名和模板。

在 Spring Boot 的 application.properties 文件中添加短信服务的 AccessKeyId 和 AccessKeySecret。

aliyun.sms.access-key-id=<AccessKeyId>
aliyun.sms.access-key-secret=<AccessKeySecret>

2.3. 创建验证码接口

创建一个 SmsCodeSender 接口,用于向指定的手机号码发送验证码。

public interface SmsCodeSender {
    void send(String mobile, String code);
}

3. 实现

3.1. 编写配置类

我们需要编写一个 Spring Security 配置类,并继承 WebSecurityConfigurerAdapter 类。在此类中,我们将配置短信验证码的过滤器。

@Configuration
public class SmsCodeSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler successHandler; // 登录成功的处理器

    @Autowired
    private AuthenticationFailureHandler failureHandler; // 登录失败的处理器

    @Autowired
    private SmsCodeSender smsCodeSender; // 短信验证码发送器

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(failureHandler);

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService());
        smsCodeAuthenticationProvider.setSmsCodeSender(smsCodeSender);

        http
            .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 添加短信验证码校验过滤器
            .authenticationProvider(smsCodeAuthenticationProvider); // 添加短信验证码校验 Provider
    }
}

configure() 方法中,我们添加了一个短信验证码校验过滤器,将它添加到了用户名密码校验过滤器前面,以保证在进行用户名密码校验之前,先进行短信验证码校验。

3.2. 编写过滤器

我们需要编写一个 SmsCodeAuthenticationFilter 类,用于进行短信验证码校验。

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String MOBILE_KEY = "mobile";

    private String mobileParameter = MOBILE_KEY;
    private boolean postOnly = true;

    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login/mobile", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String mobile = obtainMobile(request);
        if (StringUtils.isBlank(mobile)) {
            throw new AuthenticationServiceException("Mobile must not be empty.");
        }

        String code = obtainCode(request);
        if (StringUtils.isBlank(code)) {
            throw new AuthenticationServiceException("Code must not be empty.");
        }

        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile, code);
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }

    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter("code");
    }

    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    public void setMobileParameter(String mobileParameter) {
        this.mobileParameter = mobileParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMobileParameter() {
        return mobileParameter;
    }
}

此过滤器继承了 AbstractAuthenticationProcessingFilter 类,并实现了其中的 attemptAuthentication() 方法。在这个方法中,我们从请求中获取手机号码和验证码,并将其封装到 SmsCodeAuthenticationToken 类中,然后调用 getAuthenticationManager().authenticate(authRequest) 方法进行校验。

3.3. 编写 Provider

我们需要编写一个 SmsCodeAuthenticationProvider 类,用于进行短信验证码校验。

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsService userDetailsService;

    private SmsCodeSender smsCodeSender;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String mobile = (String) authentication.getPrincipal();
        String code = (String) authentication.getCredentials();

        UserDetails user = userDetailsService.loadUserByUsername(mobile);
        if (user == null) {
            throw new UsernameNotFoundException("Mobile not found: " + mobile);
        }

        // 调用短信验证码发送器发送验证码
        if (!smsCodeSender.send(mobile, code)) {
            throw new AuthenticationServiceException("Invalid sms code.");
        }

        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, code, user.getAuthorities());
        authenticationResult.setDetails(authentication.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public void setSmsCodeSender(SmsCodeSender smsCodeSender) {
        this.smsCodeSender = smsCodeSender;
    }
}

在这个 Provider 中,我们调用了短信验证码发送器的 send() 方法,进行验证码的校验。如果校验成功,就返回一个 SmsCodeAuthenticationToken 对象。

3.4. 编写发送器

我们需要编写一个 AliyunSmsCodeSender 类,用于实现 SmsCodeSender 接口。

@Component
public class AliyunSmsCodeSender implements SmsCodeSender {
    @Autowired
    private AliSmsProperties smsProperties;

    private static final String SMS_TEMPLATE_CODE = "SMS_123456789";

    @Override
    public void send(String mobile, String code) {
        IClientProfile profile = DefaultProfile.getProfile(smsProperties.getRegionId(), smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
        DefaultProfile.addEndpoint(smsProperties.getEndpointName(), smsProperties.getRegionId(), smsProperties.getProduct(), smsProperties.getDomain());
        IAcsClient client = new DefaultAcsClient(profile);
        SendSmsRequest request = new SendSmsRequest();
        request.setPhoneNumbers(mobile);
        request.setSignName(smsProperties.getSignName());
        request.setTemplateCode(SMS_TEMPLATE_CODE);
        request.setTemplateParam("{\"code\":\"" + code + "\"}");
        try {
            client.getAcsResponse(request);
        } catch (ClientException e) {
            e.printStackTrace();
            throw new RuntimeException("发送短信验证码失败!");
        }
    }
}

在这个类中,我们使用了阿里云 SDK 将验证码发送到指定的手机号码。

3.5. 编写登录接口

在 Spring Boot 应用程序的 UserController 类中,我们需要编写一个发送短信验证码的接口。

@RestController
public class UserController {
    @Autowired
    private SmsCodeSender smsCodeSender;

    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/code")
    public String sendSmsCode(HttpServletRequest request) {
        String mobile = request.getParameter("mobile");
        UserDetails user = userDetailsService.loadUserByUsername(mobile);
        if (user == null) {
            throw new UsernameNotFoundException("Mobile not found: " + mobile);
        }
        String code = RandomStringUtils.randomNumeric(6);
        smsCodeSender.send(mobile, code);
        return "success";
    }
}

在这个接口的实现中,我们从请求中获取手机号码,并生成六位数字的验证码,然后调用 smsCodeSender.send() 方法将验证码发送到指定的手机号码中。

3.6. 添加登录成功和失败的处理器

在 Spring Security 配置类中,我们需要添加登录成功和失败的处理器。

@Configuration
public class SmsCodeSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler successHandler; // 登录成功的处理器

    @Autowired
    private AuthenticationFailureHandler failureHandler; // 登录失败的处理器

    @Autowired
    private SmsCodeSender smsCodeSender; // 短信验证码发送器

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(failureHandler);

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService());
        smsCodeAuthenticationProvider.setSmsCodeSender(smsCodeSender);

        http
            .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 添加短信验证码校验过滤器
            .authenticationProvider(smsCodeAuthenticationProvider) // 添加短信验证码校验 Provider
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .successHandler(successHandler) // 登录成功的处理器
                .failureHandler(failureHandler) // 登录失败的处理器
            .and()
                .logout()
                .permitAll()
            .and()
                .authorizeRequests()
                .antMatchers("/", "/home", "/code").permitAll()
                .anyRequest().authenticated()
            .and()
                .csrf()
                .disable();
    }
}

4. 示例

4.1. 示例 1

注:此文本中为示例内容,具体的发送验证码接口实现可能有所不同。

  1. 用户点击登录按钮。
  2. 前端向后端发送请求 /code?mobile=13412341234,后端向手机号码 13412341234 发送验证码。
  3. 用户输入验证码 654321,点击登录。
  4. 前端向后端发送请求 /login/mobile,后端接收到请求,并判断短信验证码是否正确。如果正确,返回登录成功;否则,返回登录失败。

4.2. 示例 2

注:此文本中为示例内容,具体的发送验证码接口实现可能有所不同。

  1. 用户点击登录按钮。
  2. 前端向后端发送请求 /code?mobile=13912341234,后端向手机号码 13912341234 发送验证码。
  3. 用户输入验证码 123456,点击登录。
  4. 前端向后端发送请求 /login/mobile,后端接收到请求,并判断短信验证码是否正确。如果正确,返回登录成功;否则,返回登录失败。

以上就是 Spring Boot 和 Spring Security 发送短信验证码的实现攻略,希望对你有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+Security 发送短信验证码的实现 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • 什么是Java编译期注解?

    Java编译期注解是一种在Java编译时期处理的注解,它通过在源代码上附加注释信息的方式,在Java程序编译期处理中对注解进行分析并进行特定处理,从而可以在程序运行期间实现一些自定义功能。 以下是Java编译期注解的一些使用攻略: 1. 创建注解类 首先,我们需要定义一个注解类。注意,注解类的定义必须加上 @interface,以表示它是一个注解。 @Ret…

    Java 2023年5月11日
    00
  • Java的DataInputStream和DataOutputStream数据输入输出流

    DataInputStream和DataOutputStream是Java中常用的数据输入输出流,它们提供了一种用于串行化和反串行化基本java数据类型的方法。在处理二进制数据时,这两个类可以很好的对数据进行读和写操作。下面就来详细讲解这两个输入输出流的使用。 DataInputStream DataInputStream是一种基于字节流的数据输入流。在使用…

    Java 2023年5月26日
    00
  • Java中的泛型方法详解及简单实例

    Java中的泛型方法详解及简单实例 什么是泛型方法? 泛型方法是具有参数化类型的方法。所谓参数化类型,即类型形参用作方法参数类型或返回类型。Java语言支持在类和接口中定义泛型方法,当然也可以在方法中定义泛型方法。 泛型方法简化了我们对一个类中泛型参数类型的定义,使得我们能够更容易地实现代码的复用。 泛型方法的定义 泛型方法定义的通用格式: 修饰符 <…

    Java 2023年5月26日
    00
  • java加密算法–MD5加密和哈希散列带秘钥加密算法源码

    下面我来详细讲解Java加密算法——MD5加密和哈希散列带秘钥加密算法源码的完整攻略。 MD5加密算法 概述 MD5(Message Digest Algorithm)是一种单向的哈希算法,可以将任意长度的数据加密成一个128位的二进制串。MD5算法将数据经过多次非线性函数变换和数据干扰后,生成一个唯一的128位散列码,具有很高的安全性,被广泛应用于数据的完…

    Java 2023年5月19日
    00
  • 什么是双亲委派模型?

    以下是关于双亲委派模型的详细讲解: 什么是双亲委派模型? 双亲委派模型是一种类加载机制,它是由 Java 虚拟机(JVM)实现的。在双亲委派模型中,当一个类加载器收到类加载请求时,它首先将请求委派给父类加载器,如果父类加载器无法加载该类,则将请求委派给其子类加载器。这个过程会一直持续到顶层的启动类加载器,如果启动类加载器无法加载该类,则会抛出 ClassNo…

    Java 2023年5月12日
    00
  • 解决Springboot启动报错:类文件具有错误的版本61.0,应为 52.0

    针对SpringBoot启动报错“类文件具有错误的版本61.0,应为52.0”,按照以下步骤进行解决: 1.问题原因 这个问题通常是因为编译器和运行环境版本不一致。使用较高版本的编译器编译的类文件,在低版本的运行环境中无法运行,导致启动失败。 2.解决过程 2.1 确认编译器和运行环境版本 首先需要确认代码使用的编译器版本以及部署环境的JDK版本是否一致。可…

    Java 2023年5月19日
    00
  • MyBatis学习笔记(二)之关联关系

    下面是详细讲解“MyBatis学习笔记(二)之关联关系”的完整攻略。 MyBatis学习笔记(二)之关联关系 在MyBatis中,关联关系可以通过一对一、一对多、多对多的方式进行映射。接下来我们来讲解一下各种关联关系的应用。 一对一关联映射 关联映射原理 一对一的关联映射可以映射为实体类中的JavaBean,也可以映射为另外一个实体类。在映射为实体类的Jav…

    Java 2023年5月20日
    00
  • SpringMVC实现文件上传下载的全过程

    OK,SpringMVC实现文件上传下载的全过程可以包含以下几个步骤: 添加MultipartResolver配置 在SpringMVC配置文件中,添加MultipartResolver配置,用于处理文件上传的请求。示例代码如下: <bean id="multipartResolver" class="org.spring…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部