java手机验证码登录_Spring Security 实现手机验证码登录(示例代码)

思路:参考用户名密码登录过滤器链,重写认证和授权

示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出):

由于涉及内容较多,建议先复制到本地工程中,然后在细细研究。

1.   新建Maven项目  sms-code-validate

2.   pom.xml

http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

com.java

sms-code-validate

1.0.0

org.springframework.boot

spring-boot-starter-parent

2.0.5.RELEASE

org.springframework.boot

spring-boot-starter-web

org.springframework.cloud

spring-cloud-starter-oauth2

2.0.0.RELEASE

org.springframework.boot

spring-boot-starter-jdbc

com.alibaba

druid

1.1.11

mysql

mysql-connector-java

org.apache.commons

commons-lang3

org.springframework

springloaded

1.2.8.RELEASE

provided

org.springframework.boot

spring-boot-devtools

provided

${project.artifactId}

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

UTF-8

org.springframework.boot

spring-boot-maven-plugin

repackage

3.   启动类  SmsCodeStarter.java

packagecom.java;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/***

 
 

*

* 主启动类

*

*

*

*@authorLogan

**/@SpringBootApplicationpublic classSmsCodeStarter {public static voidmain(String[] args) {

SpringApplication.run(SmsCodeStarter.class, args);

}

}

4.   ValidateCode.java

packagecom.java.validate.code;importjava.time.LocalDateTime;/*** 验证码封装类

*

*@authorLogan

**/

public classValidateCode {/*** 验证码*/

privateString code;/*** 过期时间*/

privateLocalDateTime expireTime;/*** 指定验证码和有效分钟数的构造方法

*

*@paramcode 验证码

*@paramvalidityMinutes 有效分钟数*/

public ValidateCode(String code, intvalidityMinutes) {this.code =code;this.expireTime =LocalDateTime.now().plusMinutes(validityMinutes);

}/*** 指定验证码和过期时间的构造方法

*

*@paramcode 验证码

*@paramexpireTime 过期时间*/

publicValidateCode(String code, LocalDateTime expireTime) {this.code =code;this.expireTime =expireTime;

}publicString getCode() {returncode;

}publicLocalDateTime getExpireTime() {returnexpireTime;

}

}

5.   CodeGenerator.java

packagecom.java.validate.generator;importorg.apache.commons.lang3.RandomStringUtils;importcom.java.validate.code.ValidateCode;/*** 验证码生成器

*

*@authorLogan

**/

public classCodeGenerator {/*** 验证码生成方法

*

*@paramlength 验证码长度

*@paramvalidityMinutes 过期分钟数

*@return

*/

public static ValidateCode generate(int length, intvalidityMinutes) {

String code=RandomStringUtils.randomNumeric(length);return newValidateCode(code, validityMinutes);

}

}

6.   ValidateCodeSender.java

packagecom.java.validate.sender;importjava.io.IOException;importjava.io.PrintWriter;importjavax.servlet.http.HttpServletResponse;importorg.springframework.stereotype.Component;/*** 验证码发送器

*

*@authorLogan

**/@Componentpublic classValidateCodeSender {/*** 模拟发送手机验证码,此处发回浏览器,实际情况根据短信服务商做调整

*

*@paramresponse HTTP响应对象

*@parammobile 手机号

*@paramcode 验证码*/

public voidsendSmsCode(HttpServletResponse response, String mobile, String code) {

System.out.println(String.format("模拟向手机号【%s】发送验证码【%s】", mobile, code));

write(response,"验证码为:" +code);

}/*** 发送HTTP响应信息

*

*@paramresponse HTTP响应对象

*@parammessage 信息内容*/

private voidwrite(HttpServletResponse response, String message) {

response.setContentType("text/html; charset=UTF-8");

PrintWriter writer= null;try{

writer=response.getWriter();

writer.write(message);

writer.flush();

}catch(IOException e) {

e.printStackTrace();

}finally{

writer.close();

}

}

}

7.   ValidateCodeFilter.java

packagecom.java.validate.filter;importjava.io.IOException;importjava.io.PrintWriter;importjava.time.LocalDateTime;importjava.util.ArrayList;importjava.util.List;importjavax.servlet.FilterChain;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.commons.lang3.StringUtils;importorg.springframework.stereotype.Component;importorg.springframework.web.bind.ServletRequestBindingException;importorg.springframework.web.bind.ServletRequestUtils;importorg.springframework.web.filter.OncePerRequestFilter;importcom.java.controller.ValidateCodeController;importcom.java.validate.code.ValidateCode;/*** 校验验证码过滤器

*

*@authorLogan

* @createDate 2019-02-07

**/@Componentpublic class ValidateCodeFilter extendsOncePerRequestFilter {/*** 需要校验短信验证码的请求*/

private List smsCodeUrls = new ArrayList<>();

@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throwsServletException, IOException {/*** 如果需要校验短信验证码的请求集合中,包含当前请求,则进行短信验证码校验*/

if(smsCodeUrls.contains(request.getRequestURI())) {if(smsCodeValid(request, response)) {//校验通过,继续向后执行

filterChain.doFilter(request, response);

}

}//其它请求,直接放过

else{

filterChain.doFilter(request, response);

}

}

@Overrideprotected void initFilterBean() throwsServletException {//初始化添加需要校验的请求到集合中,可由配置文件中配置,此处为了简洁,直接添加

smsCodeUrls.add("/login/mobile");

}/*** 短信验证码是否有效

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security可以很方便地实现手机验证码登录,步骤如下: 1. 添加spring-security-web和spring-security-config依赖。 ```xml <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security.version}</version> </dependency> ``` 2. 创建一个实现UserDetailsService接口的类,该类用于根据不同的用户名加载用户信息。 ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.selectByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户名不存在"); } return new UserPrincipal(user); } } ``` 3. 创建一个实现AuthenticationProvider接口的类,该类用于验证用户的手机号和验证码是否正确。 ```java @Service public class SmsCodeAuthenticationProvider implements AuthenticationProvider { @Autowired private RedisTemplate<String, String> redisTemplate; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication; String mobile = authenticationToken.getPrincipal().toString(); String code = authenticationToken.getCredentials().toString(); String redisCode = redisTemplate.opsForValue().get(SmsCodeAuthenticationFilter.REDIS_SMS_CODE_KEY_PREFIX + mobile); if (StringUtils.isBlank(redisCode)) { throw new BadCredentialsException("验证码不存在或已过期"); } if (!StringUtils.equals(code, redisCode)) { throw new BadCredentialsException("验证码不正确"); } UserDetails userDetails = new UserPrincipal(new User(mobile, "", Collections.emptyList())); return new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); } } ``` 4. 创建一个实现AuthenticationFilter接口的类,该类用于处理短信验证码登录请求。 ```java public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile"; public static final String SPRING_SECURITY_FORM_CODE_KEY = "code"; public static final String REDIS_SMS_CODE_KEY_PREFIX = "SMS_CODE_"; private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY; private String codeParameter = SPRING_SECURITY_FORM_CODE_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("不支持的请求方式: " + request.getMethod()); } String mobile = obtainMobile(request); String code = obtainCode(request); if (StringUtils.isBlank(mobile)) { throw new UsernameNotFoundException("手机号不能为空"); } if (StringUtils.isBlank(code)) { throw new BadCredentialsException("验证码不能为空"); } 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(codeParameter); } protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } public void setMobileParameter(String mobileParameter) { this.mobileParameter = mobileParameter; } public void setCodeParameter(String codeParameter) { this.codeParameter = codeParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getMobileParameter() { return mobileParameter; } public final String getCodeParameter() { return codeParameter; } } ``` 5. 在WebSecurityConfigurerAdapter的子类中进行配置。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private SmsCodeAuthenticationProvider smsCodeAuthenticationProvider; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/login/mobile").permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage("/login").defaultSuccessURL("/home").permitAll() .and() .logout().logoutUrl("/logout").permitAll() .and() .addFilterBefore(smsCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(smsCodeAuthenticationProvider) .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SmsCodeAuthenticationFilter smsCodeAuthenticationFilter() { SmsCodeAuthenticationFilter filter = new SmsCodeAuthenticationFilter(); filter.setAuthenticationManager(authenticationManager()); filter.setAuthenticationSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.write("{\"code\": 0, \"message\": \"登录成功\"}"); out.flush(); out.close(); }); filter.setAuthenticationFailureHandler((request, response, exception) -> { response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.write("{\"code\": 1, \"message\": \"" + exception.getMessage() + "\"}"); out.flush(); out.close(); }); return filter; } } ``` 6. 在前端页面中添加短信验证码登录的表单。 ```html <form action="/login/mobile" method="post"> <div> <label>手机号:</label> <input type="text" name="mobile" /> </div> <div> <label>验证码:</label> <input type="text" name="code" /> <button type="button" onclick="sendSmsCode()">发送验证码</button> </div> <div> <button type="submit">登录</button> </div> </form> ``` 7. 在后端控制器中添加发送短信验证码的接口。 ```java @RestController public class SmsCodeController { @Autowired private RedisTemplate<String, String> redisTemplate; @GetMapping("/sms/code") public void sendSmsCode(String mobile) { String code = RandomStringUtils.randomNumeric(6); redisTemplate.opsForValue().set(SmsCodeAuthenticationFilter.REDIS_SMS_CODE_KEY_PREFIX + mobile, code, 5, TimeUnit.MINUTES); // 发送短信验证码 } } ``` 以上就是使用Spring Security实现手机验证码登录的全部步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值