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日

相关文章

  • GC日志有哪些级别?

    GC日志在Java应用程序中是非常重要的一部分,它可以帮助开发人员了解垃圾回收的运行情况,优化垃圾回收的效率和内存使用。GC日志一般分为以下几个级别: Verbose GC :默认情况下,JVM不会记录垃圾回收的日志。我们需要通过设置“-verbose:gc”参数来启用Verbose GC日志。Verbose GC日志主要记录了垃圾回收的时间、空间以及回收后…

    Java 2023年5月11日
    00
  • 使用IDEA配置Maven搭建开发框架ssm教程

    Sure, 我会提供一份详细的使用IDEA配置Maven搭建开发框架SSM的教程攻略。这个过程分为以下几个步骤: 1. 安装并配置Maven和MySql 首先,你需要在你的计算机上安装和配置Maven和MySql,可以参考官方文档或者在线教程。 2. 使用IDEA创建一个Maven项目 打开IDEA,点击“File” -> “New” -> “P…

    Java 2023年5月20日
    00
  • Mybatis中返回Map的实现

    Sure! MyBatis支持返回Map类型的结果集,我们可以将查询结果映射到Map中,其中Map中的key对应结果集中的字段名,value对应该字段所对应的值。那么,如何在MyBatis中实现返回Map类型的结果集呢?下面是实现的完整攻略: SQL语句 我们需要编写SQL语句,并在查询中使用别名,来保证返回结果中的属性名和表的列名保持一致。例如,以下SQL…

    Java 2023年5月19日
    00
  • JAVA 时间区间的字符串合法性验证

    下面是“JAVA 时间区间的字符串合法性验证”的完整攻略: 背景 在Java中,时间区间通常由一个开始时间和一个结束时间组成,比如“2019-01-01 00:00:00”到“2019-01-01 23:59:59”这样的字符串格式。在实际开发中,我们需要对时间区间的字符串格式进行合法性验证,保证输入数据的有效性。本文将介绍一种简单有效的JAVA时间区间字符…

    Java 2023年5月20日
    00
  • day01-项目介绍&功能实现

    项目介绍&功能实现 1.项目介绍&环境搭建 一个以社交平台为核心的轻电商项目,功能如下: 短信登录、商户查询缓存、优惠券秒杀、达人探店、好友关注、附近的商户、用户签到、UV统计 1.1项目架构 1.2项目环境搭建 1.2.1后端项目搭建 mysql的版本采用5.7及以上版本 (1)首先创建数据库,需要创建的表有: tb_user:用户表 tb…

    Java 2023年4月19日
    00
  • Springboot+hibernate实现简单的增删改查示例

    现在我将详细讲解如何用Springboot和Hibernate实现一个简单的增删改查示例,示例将包括两个部分。 简介 Springboot是一个开源的Java开发框架,可以帮助开发者快速构建高效、可扩展的web应用程序。而Hibernate则是一个Java持久化框架,通过ORM(对象关系映射)的方式来实现对象和关系数据之间的映射。通过结合使用Springbo…

    Java 2023年5月19日
    00
  • SpringBoot Http远程调用的方法

    介绍SpringBoot远程调用HTTP接口的方法主要有以下两种: 一、使用Spring的RestTemplate Pom.xml中引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-star…

    Java 2023年5月26日
    00
  • 详解IDEA中MAVEN项目打JAR包的简单方法

    下面我为您讲解详解IDEA中MAVEN项目打JAR包的简单方法,希望能帮助到您。 1. 前置条件 在进行MAVEN项目打JAR包前,需要满足以下前置条件: 安装好JDK和MAVEN; 使用IDEA开发工具。 2. 项目配置 2.1 配置pom.xml文件 在项目的pom.xml文件中,需要添加以下配置信息: <!– 打包方式为jar –> &…

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