下面详细讲解 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
注:此文本中为示例内容,具体的发送验证码接口实现可能有所不同。
- 用户点击登录按钮。
- 前端向后端发送请求
/code?mobile=13412341234
,后端向手机号码13412341234
发送验证码。 - 用户输入验证码
654321
,点击登录。 - 前端向后端发送请求
/login/mobile
,后端接收到请求,并判断短信验证码是否正确。如果正确,返回登录成功;否则,返回登录失败。
4.2. 示例 2
注:此文本中为示例内容,具体的发送验证码接口实现可能有所不同。
- 用户点击登录按钮。
- 前端向后端发送请求
/code?mobile=13912341234
,后端向手机号码13912341234
发送验证码。 - 用户输入验证码
123456
,点击登录。 - 前端向后端发送请求
/login/mobile
,后端接收到请求,并判断短信验证码是否正确。如果正确,返回登录成功;否则,返回登录失败。
以上就是 Spring Boot 和 Spring Security 发送短信验证码的实现攻略,希望对你有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringBoot+Security 发送短信验证码的实现 - Python技术站