SpringSecurity 手机号登录功能实现

 更新时间:2023年12月15日 09:56:44   作者:JunSouth  
这篇文章主要介绍了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源码分析(条件队列)

    这篇文章主要为大家详细介绍了Java并发系列之AbstractQueuedSynchronizer源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • springboot ApplicationContextInitializer的三种使用方法小结

    springboot ApplicationContextInitializer的三种使用方法小结

    这篇文章主要介绍了关于ApplicationContextInitializer的三种使用方法小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Alibaba Fastjson之超好用的JOSN解析库

    Alibaba Fastjson之超好用的JOSN解析库

    这篇文章主要介绍了Alibaba Fastjson之超好用的JOSN解析库,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • MyBatis 详细讲解动态 SQL的使用

    MyBatis 详细讲解动态 SQL的使用

    动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦
    2022-04-04
  • Java 字符串连接的性能问题分析

    Java 字符串连接的性能问题分析

    这篇文章主要介绍了Java 字符串连接的性能问题分析的相关资料,需要的朋友可以参考下
    2017-03-03
  • Spring注入值到Bean的三种方式

    Spring注入值到Bean的三种方式

    这篇文章主要为大家详细介绍了Spring注入值到Bean的三种方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • Spring实战之Bean定义中的SpEL表达式语言支持操作示例

    Spring实战之Bean定义中的SpEL表达式语言支持操作示例

    这篇文章主要介绍了Spring实战之Bean定义中的SpEL表达式语言支持操作,结合实例形式分析了Bean定义中的SpEL表达式语言操作步骤与实现技巧,需要的朋友可以参考下
    2019-12-12
  • SpringSecurity的防Csrf攻击实现代码解析

    SpringSecurity的防Csrf攻击实现代码解析

    这篇文章主要介绍了SpringSecurity的防Csrf攻击实现代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Nacos简介最新收藏版

    Nacos简介最新收藏版

    Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台,Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台,对Nacos相关基本知识感兴趣的朋友一起看看吧
    2023-08-08
  • Java中的functor实现

    Java中的functor实现

    Java中的functor实现...
    2006-12-12

最新评论