SpringSecurity 手机号登录功能实现
一、工作流程
1.向手机发送验证码,第三方短信发送平台,如阿里云短信。
2.手机获取验证码后,在表单中输入验证码。
3.使用自定义过滤器SmsCodeValidateFilter。
4.短信校验通过后,使用自定义手机认证过滤器SmsCodeAuthenticationFilter校验手机号码是否存在。
5.自定义SmsCodeAuthenticationToken提供给SmsCodeAuthenticationFilter。
6.自定义SmsCodeAuthenticationProvider提供给AuthenticationManager。
7.创建针对手机号查询用户信息的SmsCodeUserDetailsService,提交给。SmsCodeAuthenticationProvider。
8.自定义SmsCodeSecurityConfig配置类将上面组件连接起来。
9.将SmsCodeSecurityConfig添加到LearnSrpingSecurity安全配置的过滤器链上。
二、实现
2.1、验证码生成、发送
/** * 创建验证码生成器 */ @Component public class SmsCodeGenerator { public String generate() { return RandomStringUtils.randomNumeric(4); } } /** * 验证码发送器 */ @Component public class SmsCodeSender { public void send(String mobile, String code) { System.out.println("向手机" + mobile + "发送短信验证码" + code); } } /** * 发送短信接口 */ @RestController public class ValidateCodeController { @Autowired private SmsCodeGenerator smsCodeGenerator; @Resource private SmsCodeSender smsCodeSender; @Resource private RedisTemplate redisTemplate; @GetMapping("/code/sms") public String createSmsCode(@RequestParam String mobile) throws IOException { //获取验证码 String smsCode = smsCodeGenerator.generate(); //把验证码设置到redis redisTemplate.boundValueOps(SecurityConstants.getValidCodeKey(mobile)).set(smsCode, 300, TimeUnit.SECONDS); smsCodeSender.send("18360903475", "登录验证码为:" + smsCode + ",五分钟过期"); return "验证码是 : " + smsCode; } }
2.2、手机号码认证 Token
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import java.util.Collection; /** * 手机号码认证 Token */ public class PhoneNumAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * principal的作用有两个, 在未登录之前是用户名,那么在登录之后是用户的信息。 */ private final Object principal; /** * 构造 * @param principal 手机号码 */ public PhoneNumAuthenticationToken(Object principal) { super(null); this.principal = principal; // 用于指示AbstractSecurityInterceptor是否应向AuthenticationManager提供身份验证令牌 setAuthenticated(false); } /** * 构造 * @param principal 用户信息 * @param authorities 用户权限列表 */ public PhoneNumAuthenticationToken(Object principal,Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; // 用于指示AbstractSecurityInterceptor是否应向AuthenticationManager提供身份验证令牌 setAuthenticated(true); } /** * 正常这个是返回密码,但手机登录没有密码,不用管 */ @Override public Object getCredentials() { return null; } /** * 获取手机号或用户信息 */ @Override public Object getPrincipal() { return this.principal; } }
2.3、拦截请求、获取手机号码
/** * 手机号码拦截器, 获取手机号码 */ public class PhoneNumAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public PhoneNumAuthenticationFilter() { super(new AntPathRequestMatcher("/phoneLogin", "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!Objects.equals(request.getMethod(),"POST")) { throw new AuthenticationServiceException("身份验证方法需为:'POST'请求"); } // 获取手机号 String phoneNum = Optional.ofNullable(request.getParameter(Constants.PHONE_NUM_PARAMETER)).map(String::trim).orElse(""); // new 手机号码验证Token PhoneNumAuthenticationToken authRequest = new PhoneNumAuthenticationToken(phoneNum); // 身份验证详细信息 authRequest.setDetails(super.authenticationDetailsSource.buildDetails(request)); return this.getAuthenticationManager().authenticate(authRequest); } }
2.4、短信验证码验证过滤器
/** * 短信验证码验证过滤器 */ @Component public class SmsCodeFilter extends OncePerRequestFilter { @Resource private StringRedisTemplate stringRedisTemplate; @Resource private CustomizeAuthencationFailureHandler customizeAuthencationFailureHandler; @Override protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException { /** * uri = /phoneLogin 即手机号码登录才拦截 */ if (Objects.equals(Constants.SMS_LOGIN_URI,request.getRequestURI())) { try{ // 验证手机验证码 validateProcess(request); }catch (AuthenticationException ex) { customizeAuthencationFailureHandler.onAuthenticationFailure(request, response, ex); return; } } filterChain.doFilter(request, response); } /** * 验证手机验证码 */ private void validateProcess(HttpServletRequest request){ // 获取手机号 String msgCode = stringRedisTemplate.opsForValue().get(Constants.SMS_CODE_SESSION_KEY); String code = request.getParameter(Constants.MSG_CODE); if(Strings.isBlank(code)) { throw new InternalAuthenticationServiceException("短信验证码不能为空."); } if(null == msgCode) { throw new InternalAuthenticationServiceException("短信验证码已失效."); } if(!code.equals(msgCode)) { throw new InternalAuthenticationServiceException("短信验证码错误."); } } }
2.5、继承 WebSecurityConfigurerAdapter 配置 HttpSecurity
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 数据源 */ @Resource private DataSource dataSource; /** * 用户信息服务 */ @Resource private UserAuthentication userAuthentication; /** * 成功处理 */ @Resource private CustomizeAuthencationSuccessHandler customizeAuthencationSuccessHandler; /** * 失败处理 */ @Resource private CustomizeAuthencationFailureHandler customizeAuthencationFailureHandler; /** * 用户登出处理 */ @Resource private UserLogoutSuccessHandler userLogoutSuccessHandler; /** * 多用户登录处理 */ @Resource private MutilpleSessionHandler mutilpleSessionHandler; /** * 密码编码器 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 手机号码登录验证处理 */ @Resource private DaoPhoneNumAuthenticationProvider daoPhoneNumAuthenticationProvider; /** * 信息验证码过滤器 */ @Resource private SmsCodeFilter smsCodeFilter; /** * 把AuthenticationManager公开 */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * 配置自定义验证查询/加密工具 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userAuthentication).passwordEncoder(passwordEncoder()); } /** * 手机号码登录拦截器 */ @Bean public PhoneNumAuthenticationFilter phoneNumAuthenticationFilter() throws Exception { // 手机号码拦截器, 获取手机号码 PhoneNumAuthenticationFilter phoneNumAuthenticationFilter = new PhoneNumAuthenticationFilter(); phoneNumAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); //使用手机号登录失败了如何处理 phoneNumAuthenticationFilter.setAuthenticationFailureHandler(customizeAuthencationFailureHandler); // 使用手机号登录成功了如何处理 phoneNumAuthenticationFilter.setAuthenticationSuccessHandler(customizeAuthencationSuccessHandler); return phoneNumAuthenticationFilter; } @Override protected void configure(HttpSecurity http) throws Exception { http // 加入短信验证码过滤器 .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) // 加入手机号码登录过滤器 .addFilterAfter(phoneNumAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 加入手机号码登录验证提供者 .authenticationProvider(daoPhoneNumAuthenticationProvider) // 表单登录 .formLogin() // 未登录跳转登录页面 .loginPage("/login.html") // 指定登录路径 .loginProcessingUrl("/login") // 用户登录成功的处理 .successHandler(customizeAuthencationSuccessHandler) // 用户登录失败的处理 .failureHandler(customizeAuthencationFailureHandler) // 因为用户传入过来的token, 需要再次进行校验 .userDetailsService(userAuthentication) .tokenValiditySeconds(3600) // .alwaysRemember(true) // 认证配置 .and() .authorizeRequests() //不拦截的Url .antMatchers("/login.html", "/image/code", "/smsCode", "/css/**", "/js/**", "/phoneLogin").permitAll() .anyRequest() //所有的请求 .authenticated() //认证之后就可以访问 // 多端登录限制,限制一个账号同时只能一个人登录 .and() .sessionManagement() .maximumSessions(1) .expiredSessionStrategy(mutilpleSessionHandler) .and() // 登出配置 .and() .logout() .logoutUrl("/logout") // 登出成功处理 .logoutSuccessHandler(userLogoutSuccessHandler) .and() .csrf().disable(); } }
到此这篇关于SpringSecurity 手机号登录的文章就介绍到这了,更多相关SpringSecurity 手机号登录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java并发系列之AbstractQueuedSynchronizer源码分析(条件队列)
这篇文章主要为大家详细介绍了Java并发系列之AbstractQueuedSynchronizer源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2018-02-02springboot ApplicationContextInitializer的三种使用方法小结
这篇文章主要介绍了关于ApplicationContextInitializer的三种使用方法小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-11-11Spring实战之Bean定义中的SpEL表达式语言支持操作示例
这篇文章主要介绍了Spring实战之Bean定义中的SpEL表达式语言支持操作,结合实例形式分析了Bean定义中的SpEL表达式语言操作步骤与实现技巧,需要的朋友可以参考下2019-12-12
最新评论