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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ROS2 Control分析讲解

ROS2 Control 文章目录 前言简述组成安装 框架Controller ManagerResource ManagerControllersUser Interfaces Hardware ComponentsURDF中的硬件描述机器人运行框架 总结 前言 ros2_control是一个使用&#xff08;ROS 2&#xff09;进行机器人&#xff08;实时&#xff09;控…

如何用开关电源测试系统测试电源峰值电流?

一、用万用表、示波器测量峰值电流 首先将待测电路输入信号线分别连接到测试电路的输入端和地端。待测电路的电源端连接电源。然后将示波器设置为AC耦合模式&#xff0c;通道1连接待测电路输入端&#xff0c;通道2连接待测电路地端。调整数字万用表为电流测量模式。打开电源&am…

使用VeryFL【02】python环境安装

新建虚拟环境 conda create --name vfl python3.7激活新建的虚拟环境 conda activate vfl安装pytorch 安装Brownie pip install eth-brownie -i https://pypi.tuna.tsinghua.edu.cn/simple

一款计算机顶会爬取解析系统 paper info

一款计算机顶会爬取解析系统 paper info 背景项目实现的功能 技术方案架构设计项目使用的技术选型 使用方法本地项目部署使用ChatGPT等大模型创建一个ChatGPT助手使用阿里云 顶会数据量 百度网盘pfd文件json文件 Q&A github链接 &#xff1a;https://github.com/codebricki…

Nginx+Tomcat实现负载均衡和动静分离

目录 前瞻 动静分离和负载均衡原理 实现方法 实验&#xff08;七层代理&#xff09; 部署Nginx负载均衡服务器(192.168.75.50:80) 部署第一台Tomcat应用服务器&#xff08;192.168.75.60:8080&#xff09; 多实例部署第二台Tomcat应用服务器&#xff08;192.168.75.70:80…

LOF基金跟股票一样吗?

LOF基金&#xff0c;全称为"上市型开放式基金"&#xff0c;是一种可以在上海证券交易所认购、申购、赎回及交易的开放式证券投资基金。投资者可以通过上海证券交易所场内证券经营机构或场外基金销售机构进行认购、申购和赎回基金份额。 LOF基金的特点是既可以像股票…

DataGrip连接Hive以及MySQL

如果连接失败&#xff0c;是因为useSSL ,改成NO或者False;

Spring Cloud + Vue前后端分离-第5章 单表管理功能前后端开发

Spring Cloud Vue前后端分离-第5章 单表管理功能前后端开发 完成单表的增删改查 控台单表增删改查的前后端开发&#xff0c;重点学习前后端数据交互&#xff0c;vue ajax库axios的使用等 通用组件开发:分页、确认框、提示框、等待框等 常用的公共组件:确认框、提示框、等待…

时序分解 | Matlab实现DBO-VMD基于蜣螂优化算法优化VMD变分模态分解时间序列信号分解

时序分解 | Matlab实现DBO-VMD基于蜣螂优化算法优化VMD变分模态分解时间序列信号分解 目录 时序分解 | Matlab实现DBO-VMD基于蜣螂优化算法优化VMD变分模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.利用蜣螂优化算法优化VMD中的参数k、a&…

【PostgreSQL】从零开始:(四)使用PgAdmin4连接数据库,及工具使用

登陆pgAdmin4 连接数据库 填写连接名称 填写连接信息 错误信息如下 解决办法 1.登陆数据库服务器切换到postgres用户 [rootpostgre-sql ~]# su - postgres 上一次登录&#xff1a;三 12月 13 18:10:00 CST 2023pts/0 上 [postgrespostgre-sql ~]$ 2.查看数据库进程 [postgre…

Linux——MySQL备份与恢复

一、数据库备份概述 1、数据备份的重要性 在企业中数据的价值至关重要&#xff0c;数据保障了企业业务的正常运行。因此&#xff0e;数据的安全性及数据的可靠性是运维的重中之重&#xff0c;任何数据的丢失都可能对企业产生严重的后果。通常情况下造成数据丢失的原因有如下几种…

JVM 详解(JVM组成部分、双亲委派机制、垃圾回收算法、回收器、回收类型、了解调优思路)

目录 JVM 详解&#xff08;JVM组成部分、双亲委派机制、垃圾回收算法、回收器、回收类型、了解调优思路&#xff09;1、概念&#xff1a;什么是 JVM ?JVM 的作用&#xff1f; 2、JVM 的主要组成部分&#xff1f;类加载器&#xff08;Class Loader&#xff09;&#xff1a;简单…

ECharts实现数据可视化入门教程

ECharts介绍 Apache ECharts ECharts是一个使用 JavaScript 实现的开源可视化库 入门教程 第一步&#xff1a;下载echarts.js文件 下载地址&#xff1a;下载 - Apache ECharts 点击Dist 点击echarts.min.js并保存 将下载好的.js文件引入到项目的js文件中 第二步&#xff1a;…

[多线程]线程池

目录 1.前言 2. Java中的线程池以及参数介绍 2.1 核心线程数和最大线程数 2.2最大空闲存活时间 2.3任务队列和线程工厂 2.4 拒绝策略(最重要&#xff09; 2.5 线程池的类型 3.线程池的大小如何确定 4.手动写一个线程池 1.前言 我们知道.在开发过程中.为了效率,会引进很…

理解JSX:提高前端开发效率的关键(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

CGAL的最优传输曲线重构

1、介绍 此程序包实现了一种重建和简化二维点集的方法。输入是一组具有质量属性的二维点&#xff0c;可能受到噪声和离群值的干扰。输出是一组线段和孤立点&#xff0c;它们近似于输入点&#xff0c;如下图所示。质量属性与每个点的近似重要性有关。 左&#xff1a;输入点集受到…

SWPU NSS新生赛

&#x1f60b;大家好&#xff0c;我是YAy_17&#xff0c;是一枚爱好网安的小白&#xff0c;正在自学ing。 本人水平有限&#xff0c;欢迎各位大佬指点&#xff0c;一起学习&#x1f497;&#xff0c;一起进步⭐️。 ⭐️此后如竟没有炬火&#xff0c;我便是唯一的光。⭐️ 最近…

网页图标素材免费下载网站

这里是几个可以免费下载网页图标素材的的网站。这些个网站里的图表和素材&#xff0c;应该是都可以免费下载的。&#xff08;至少我下载了几个素材是没有花钱的&#xff09; Flaticon iconArchive freepik 4. iconmonstr 5. Icons and Photos For Everything 如果想下载图片&a…

在项目中,使用drawio创建一个共享协作看板

在项目中&#xff0c;使用drawio创建一个共享协作看板 drawio是一款强大的图表绘制软件&#xff0c;支持在线云端版本以及windows, macOS, linux安装版。 如果想在线直接使用&#xff0c;则直接输入网址draw.io或者使用drawon(桌案), drawon.cn内部完整的集成了drawio的所有功…

【C语言(十一)】

C语言内存函数 一、memcpy使用和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); • 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。 • 这个函数在遇到 \0 的时候并不会停下来。 • 如果sourc…