SpringSecurity 手机号登录

news2024/11/26 15:28:23

一、工作流程 

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();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1311592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用vue-okr-tree实现飞书OKR对齐视图

vue-okr-tree-demo 因开发需求需要做一个类似飞书OKR对齐视图的功能&#xff0c;参考了两位大神的代码&#xff1a; 开源组件vue-okr-tree作者博客地址&#xff1a;http://t.csdnimg.cn/5gNfd 对组件二次封装的作者博客地址&#xff1a;http://t.csdnimg.cn/Tjaf0 开源组件v…

oracle aq java jms使用(数据类型为XMLTYPE)

记录一次冷门技术oracle aq的使用 版本 oracle 11g 创建用户 -- 创建用户 create user testaq identified by 123456; grant connect, resource to testaq;-- 创建aq所需要的权限 grant execute on dbms_aq to testaq; grant execute on dbms_aqadm to testaq; begindbms_a…

android studio 快捷输入模板提示

在Android开发中&#xff0c;我们经常会遇到一些重复性的代码&#xff0c;例如创建一个新的Activity、定义一个Getter方法等。为了提高开发效率&#xff0c;Android Studio提供了Live Templates功能&#xff0c;可以通过简化输入来快速生成这些重复性代码。 按下图提示设置&am…

GO并发编程综合应用

一.GO并发编程综合应用 1.生产者消费者模式 1.1需求分析 ​ 生产者每秒生产一个商品&#xff0c;并通过物流公司取货 ​ 物流公司将商品运输到商铺 ​ 消费者阻塞等待商铺到货&#xff0c;需要消费10次商品 1.2实现原理 1.3代码实现&#xff1a; package mainimport (&q…

Google DeepMind发布Imagen 2文字到图像生成模型;微软在 HuggingFace 上发布了 Phi-2 的模型

&#x1f989; AI新闻 &#x1f680; Google DeepMind发布Imagen 2文字到图像生成模型 摘要&#xff1a;谷歌的Imagen 2是一种先进的文本到图像技术&#xff0c;可以生成与用户提示紧密对齐的高质量、逼真的图像。它通过使用训练数据的自然分布来生成更逼真的图像&#xff0c…

服务器上配置jupyter,提示Invalid credentials如何解决

我是按照网上教程在服务器上安装的jupyter以及进行的密码配置&#xff0c;我利用 passwd()这个口令生成的转译密码是"argon...."。按照教程配置jupyter notebook配置文件里面的内容&#xff0c;登陆网页提示"Invalid credentials"。我谷歌得到的解答是&…

07用户行为日志数据采集

用户行为数据由Flume从Kafka直接同步到HDFS&#xff0c;由于离线数仓采用Hive的分区表按天统计&#xff0c;所以目标路径要包含一层日期。具体数据流向如下图所示。 按照规划&#xff0c;该Flume需将Kafka中topic_log的数据发往HDFS。并且对每天产生的用户行为日志进行区分&am…

cfa一级考生复习经验分享系列(三)

从总成绩可以看出&#xff0c;位于90%水平之上&#xff0c;且置信区间全体均高于90%线。 从各科目成绩可以看出&#xff0c;所有科目均位于90%线上或高于90%线&#xff0c;其中&#xff0c;另类与衍生、公司金额、经济学、权益投资、固定收益、财报分析表现较好&#xff0c;目测…

多架构容器镜像构建实战

最近在一个国产化项目中遇到了这样一个场景&#xff0c;在同一个 Kubernetes 集群中的节点是混合架构的&#xff0c;也就是说&#xff0c;其中某些节点的 CPU 架构是 x86 的&#xff0c;而另一些节点是 ARM 的。为了让我们的镜像在这样的环境下运行&#xff0c;一种最简单的做法…

双端队列和优先级队列

文章目录 前言dequedeque底层设计迭代器设计 priority仿函数数组中的第k个最大元素优先级队列模拟实现pushpop调整仿函数存储自定义类型 前言 今天要介绍比较特殊的结构&#xff0c;双端队列。 还有一个适配器&#xff0c;优先级队列。 deque 栈的默认容器用了一个deque的东西…

案例课7——百度智能客服

1.公司介绍 百度智能客服是百度智能云推出的将AI技术赋能企业客服业务的一揽子解决方案。该方案基于百度世界先进的语音技术、自然语言理解技术、知识图谱等构建完备的一体化产品方案&#xff0c;结合各行业头部客户丰富的运营经验&#xff0c;持续深耕机场服务、电力调度等场…

【普中】基于51单片机简易计算器显示设计( proteus仿真+程序+设计报告+实物演示+讲解视频)

目录标题 &#x1f4df;1. 主要功能&#xff1a;&#x1f4df;2. 讲解视频&#xff1a;&#x1f4df;3. 设计说明书(报告)&#x1f4df;4. 仿真&#x1f4df;5. 实物烧录和现象&#x1f4df;6. 程序代码&#x1f4df;7. 设计资料内容清单 【普中开发板】基于51单片机简易计算器…

日志框架Log4j、JUL、JCL、Slf4j、Logback、Log4j2

为什么程序需要记录日志 我们不可能实时的24小时对系统进行人工监控&#xff0c;那么如果程序出现异常错误时要如何排查呢&#xff1f;并且系统在运行时做了哪些事情我们又从何得知呢&#xff1f;这个时候日志这个概念就出现了&#xff0c;日志的出现对系统监控和异常分析起着…

Jenkins 添加节点报错

报错日志 Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.UnsupportedClassVersionError: hudson/remoting/Launcher has been compiled by a more recent version of the Java Runtime (cl…

react+datav+echarts实现可视化数据大屏

&#x1f4d3;最近有点闲&#xff0c;就学习了下react&#xff0c;没想到就把react学完了&#xff0c;觉得还不错&#xff0c;就打算出一把reactdatav的简易版可视化数据大屏供大家做个参考。 &#x1f4d3;效果如下 1下载必要的框架 &#x1f4d3; react路由 npm install re…

Rancher中使用promtail+loki+grafna收集k8s日志并展示

Rancher中使用promtail+loki+grafna收集k8s日志并展示 根据应用需求和日志数量级别选择对应的日志收集、过滤和展示方式,当日志量不太大,又想简单集中管理查看日志时,可使用promtail+loki+grafna的方式。本文找那个loki和grafana外置在了k8s集群之外。 1、添加Chart Repo …

js解析.shp文件

效果图 原理与源码 本文采用的是shapefile.js工具 这里是他的npm地址 https://www.npmjs.com/package/shapefile 这是他的unpkg地址&#xff0c;可以点开查看源码 https://unpkg.com/shapefile0.6.6/dist/shapefile.js 这个最关键的核心问题是如何用这个工具&#xff0c;网上…

[开源更新] 企业级身份管理和访问管理系统、为数字身份安全赋能

一、系统简介 名称&#xff1a;JNPF权限管理系统 JNPF 权限管理系统可用于管理企业内员工账号、权限、身份认证、应用访问等&#xff0c;可整合部署在本地或云端的内部办公系统、业务系统及第三方 SaaS 系统的所有身份&#xff0c;实现一个账号打通所有应用的服务。其有如下几…

【Docker】WSL 2 上的 Docker 搭建和入门

▒ 目录 ▒ &#x1f6eb; 导读开发环境 1️⃣ 安装安装Docker Desktop for Windows 2️⃣ 环境配置3️⃣ hello world第一次运行再次运行分析总结 &#x1f4d6; 参考资料 &#x1f6eb; 导读 开发环境 版本号描述文章日期2023-12-14操作系统Win11 - 22H222621.2715WSL2 C:…

60.Sentinel源码分析

Sentinel源码分析 1.Sentinel的基本概念 Sentinel实现限流、隔离、降级、熔断等功能&#xff0c;本质要做的就是两件事情&#xff1a; 统计数据&#xff1a;统计某个资源的访问数据&#xff08;QPS、RT等信息&#xff09; 规则判断&#xff1a;判断限流规则、隔离规则、降级规…