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中进行字符串拼接时,有多种方式可以完成,每种方式都有其独特的优点和适用场景。以下是其中一些常用的方式: 1. “+”符号拼接字符串 使用“+”符号,可以很方便地进行字符串拼接。在代码中简单地使用“+”将字符串连接起来即可。例如: String s1 = "Hello "; String s2 = "world!&…

    Java 2023年5月26日
    00
  • java日常练习题,每天进步一点点(1)

    下面是对java日常练习题攻略的详细讲解。 1. 确定学习目标 在开始学习之前,我们必须了解我们的学习目标。在这个练习题中,我们的目标是通过每天练习一点点,提高自己的Java编程技能。 2. 确定练习内容 在了解学习目标之后,我们需要选择适合自己的练习内容。这个练习题提供了很多经典的Java练习题,包括基础语法、算法、数据结构、面向对象等方面的内容。 3. …

    Java 2023年5月23日
    00
  • springboot与springmvc基础入门讲解

    让我来为您详细讲解“springboot与springmvc基础入门讲解”的完整攻略。 简介 Spring Boot是Spring Framework的一个扩展框架,它为Spring开发者提供了更快的开发体验。Spring MVC是一个经典的MVC框架,负责接收HTTP请求并将其转换为相应的处理程序,通常由Controller和Model组成。 本文将对Sp…

    Java 2023年5月15日
    00
  • 详解MyBatis逆向工程

    详解MyBatis逆向工程攻略 MyBatis逆向工程可以快速生成Java实体类、映射文件以及Mapper接口,省去手写代码的繁琐过程。以下是详解MyBatis逆向工程的完整攻略。 步骤一:准备工作 项目中需要添加 mybatis-generator-core 依赖。 xml <dependency> <groupId>org.myb…

    Java 2023年5月19日
    00
  • web开发中添加数据源实现思路

    我来详细讲解web开发中添加数据源实现思路的完整攻略。在web开发过程中,我们需要添加数据源来提供数据支持。其中包括本地文件、数据库、网络API等多种形式。下面介绍一般的实现思路。 1. 确认数据源类型和数据格式 在添加数据源前,首先需要确认数据源的类型和数据格式。不同的数据源类型和数据格式,需要使用不同的方法进行访问和处理。比如,如果数据源是本地文件,需要…

    Java 2023年6月15日
    00
  • java的Hibernate框架报错“ConstraintViolationException”的原因和解决方法

    当使用Java的Hibernate框架时,可能会遇到“ConstraintViolationException”错误。这个错误通常是由于以下原因之一引起的: 违反了数据库约束:如果您尝试插入或更新数据时违反了数据库约束,则可能会出现此错误。在这种情况下,需要查看数据库约束并解决问题。 数据库事务问题:如果您尝试插入或更新数据时存在事务问题,则可能会出现此错误…

    Java 2023年5月4日
    00
  • Java中s.charAt(index)用于提取字符串s中的特定字符操作

    当使用Java编写代码时,经常需要操作字符串。Java中提供了许多字符串相关的方法,其中包括charAt()方法,可以用于提取字符串中特定位置的字符。在下面的攻略中,我们将详细讲解charAt()方法的用法及示例。 1. 正确使用charAt()方法 charAt()方法可以用于提取字符串中特定位置的字符。要使用该方法,必须向其传递一个参数,该参数为字符串中…

    Java 2023年5月27日
    00
  • Spring Boot Starters简介及其优劣势

    SpringBootStarters简介及其优劣势 什么是SpringBoot Starters? SpringBoot Starters是一种快速构建Spring应用程序的方式,它旨在减少开发人员的配置工作量。SpringBoot提供了一系列官方的Starters,每个Starter都预配置了一个或多个Spring应用程序需要的依赖关系。 SpringBo…

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