认证服务------遇到的问题及解决方案

news2024/11/25 4:52:53

认证服务的问题及解决方案

问题1:发送验证码

发送验证码要注意的问题

发送验证码要注意的两个问题

1、接口防刷

接口防刷就是因为发送验证码的api接口是可以通过查看页面元素看的到的

上面就可以看到发送验证码的js代码中的请求地址,可以恶意的通过postman不断的发送这个请求来消耗短信验证码的资源,更准确来说是为了防止同一个手机号疯狂的发这个请求不断的消耗发送短信的资源

2、验证码再次校验

在发送了一次验证码后通过js的限制的确不能再点发送验证码

这个是通过js实现的超链接不可用来实现不能重复发送验证码

但是刷新页面后还是可以对同一个手机号再次发送验证码,也就是我可以先对一个手机号发送一次验证码,这时虽然验证码发送链接不可用点了,但是我刷新页面后还是可以对同个手机号继续发送验证码,并且还是在60s内

解决方案

/**
* 短信验证码
* @param phone
* @return
*/
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
    
    /**
    *  接口防刷
    */
    //把验证码从缓存中提取出来
    //(缓存中验证码的格式sms:code:123456789->123456_1646981054661 ,123456789是手机号,123456表示验证码,1646981054661表示存入缓存的时间)
    String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
    if (!StringUtils.isEmpty(redisCode)) {
        //把存入缓存的验证码的值给提取出来(格式是123456_1646981054661 ,123456表示验证码,1646981054661表示存入缓存的时间)
        long currentTime = Long.parseLong(redisCode.split("_")[1]);
        //活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
        if (System.currentTimeMillis() - currentTime < 60000) {
            //60s内不能再发
            // BizCodeEnum.SMS_CODE_EXCEPTION=10002     BizCodeEnum.SMS_CODE_EXCEPTION = 10002
            return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());
        }
    }
    
    /**验证码再次检验
    * 2、创建验证码存入redis.存key-phone,value-code
    */
    int code = (int) ((Math.random() * 9 + 1) * 100000);
    String codeNum = String.valueOf(code);
    //存入缓存的验证码格式是123456_1646981054661  加系统时间是为了防止多次刷新验证码
    String redisStorage = codeNum + "_" + System.currentTimeMillis();
    
    //存入redis,防止同一个手机号在60秒内再次发送验证码(存入缓存的格式sms:code:123456789->123456 ,其中123456789是手机号,123456是验证码)
    stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,
                                          redisStorage,10, TimeUnit.MINUTES);//AuthServerConstant.SMS_CODE_CACHE_PREFIX = sms:code:
    
    //        String codeNum = UUID.randomUUID().toString().substring(0,6);
    thirdPartFeignService.sendCode(phone, codeNum);
    
    return R.ok();
    }

只需要加个时间戳存到redis中,在实现这步之前先去redis中取,然后分割出时间戳看看是不是在60s,如果是那就报错,不是就执行下面的逻辑,上面两个问题都只需要加个时间戳然后判断这个手机号是否在60s内发送了验证码即可

问题2:微服务中Session

session可以看做是服务器内存,用来存储浏览器的发过来的信息,这些session都交给sessionManager来管理,同一个域名session有效,不同域名session不能共享

微服务中Session常见的两个问题

微服务中Session常见的两个问题

  • 有多个同样的服务,域名相同,但是在负载均衡时各个服务中会session不同步问题,例如上面的两个会员服务,可能存用户信息的session存到了第一个会员服务中,然后下次这个用户登录的时候却发给了第二个会员服务,但是第二个会员服务中没有session,所以会出现session不同步问题
  • 不同服务,session不能共享问题,因为域名不同,微服务中不同的服务有着不同的域名

解决方案思路

思路一

思路二

其实就是存到浏览器中

方案一

方案二

最终采取的解决方案

使用SpringSession的目的是来解决分布式session不同步不共享的问题,首先用户登录成功后存放用户信息的session会存到会员服务里,然后会员服务把session存到redis中,然后会员服务就会给浏览器发卡(就是如上图发送一个jsessionid=1的cookie给浏览器),由于默认发卡的域名是当前服务的域名,也就是会员服务的域名,这个域名发给浏览器,但是这个域名作用域只在会员服务中有效,作用域太小了,所以需要扩大域名为父域名.gulimall.com,这样所有的服务的域名都可以访问到这个session了

整合SpringSession来解决session不同步不共享的问题

使用SpringSession的目的是来解决分布式session不同步不共享的问题。使用SpringSession可以把session都存在redis中,这样就解决了session不同步的问题,然后扩大作用域,这就解决了session不共享的问题,SpringSession不需要显性的操作(也就是不需要用StringRedisTemplate类的方法来把session放到redis中去,啥都不用干,就正常的把数据放到HttpSession中就可),由于整合了SpringSession,所以放到HttpSession中的数据会自动的放到redis中去,由于配置了序列化,所以session会被序列化json字符串放到redis中去,然后前端某个服务要取这个session的时候也会自动的redis中取


注意:由于这里使用springsession的用的类型是redis,所以这springsession和redis都要一起加入依赖和配置(所以session会被存到Redis缓存中)

(1)导入依赖

<!-- 整合springsession 来解决分布式session不同步不共享的问题-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 整合redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)在application.properties配置文件里配置springsession

#配置springsession
spring.session.store-type=redis
server.servlet.session.timeout=30m
#配置redis的ip地址
spring.redis.host=192.168.241.128

(3)在config配置中加入springSession配置类

package com.saodai.saodaimall.order.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;


/**
* springSession配置类(所有要使用session的服务的session配置要一致)
*/

@Configuration
public class SessionConfig {
    
    /**
    * 配置session(主要是为了放大session作用域)
    * @return
    */
    @Bean
    public CookieSerializer cookieSerializer() {
        
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        
        //放大作用域
        cookieSerializer.setDomainName("saodaimall.com");
        cookieSerializer.setCookieName("SAODAISESSION");
        
        return cookieSerializer;
    }
    
    
    /**
    * 配置Session放到redis存储的格式为json(其实就是json序列化)
    * @return
    */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
    
}

自定义一个配置类SessionConfig然后通过设置CookieSerializer来扩大Session的作用域,再配置json序列化

(4)在启动类上添加@EnableRedisHttpSession注解

package com.saodai.saodaimall.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * 订单服务启动类
 */
@EnableFeignClients
@EnableRedisHttpSession
@EnableDiscoveryClient
@SpringBootApplication
public class SaodaimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SaodaimallOrderApplication.class, args);
    }

}

SpringSession的原理

Spring-Session的实现就是设计一个过滤器SessionRepositoryFilter,每当有请求进入时,过滤器会首先将ServletRequest 和ServletResponse 这两个对象转换成Spring内部的包装类SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper对象,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session并重写了getSession方法来实现了session的创建和管理工作。将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
    @Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
         //使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper
		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
				request, response, this.servletContext);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
				wrappedRequest, response);
		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
            //保存session信息
			wrappedRequest.commitSession();
		}
	}
}
@Override
		public HttpSessionWrapper getSession(boolean create) {
            //获取当前Request作用域中代表Session的属性,缓存作用避免每次都从sessionRepository获取
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
            //查找客户端中一个叫SESSION的cookie,拿到sessionId,通过sessionRepository对象根据sessionId去Redis中查找
			S requestedSession = getRequestedSession();
            //如果从redis中查询到了值
			if (requestedSession != null) {
                //客户端存在sessionId 并且未过期
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.setNew(false);
                    //将Session设置到request属性中
					setCurrentSession(currentSession);
					return currentSession;
				}
			}
			else {
				// This is an invalid session id. No need to ask again if
				// request.getSession is invoked for the duration of this request
				if (SESSION_LOGGER.isDebugEnabled()) {
					SESSION_LOGGER.debug(
							"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
				}
				setAttribute(INVALID_SESSION_ID_ATTR, "true");
			}
            //不创建Session就直接返回null
			if (!create) {
				return null;
			}
			if (SESSION_LOGGER.isDebugEnabled()) {
				SESSION_LOGGER.debug(
						"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
								+ SESSION_LOGGER_NAME,
						new RuntimeException(
								"For debugging purposes only (not an error)"));
			}
            //执行到这了说明需要创建新的Session
            // 通过sessionRepository创建RedisSession这个对象
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}
	// 通过sessionRepository创建RedisSession这个对象
	@Override
	public RedisSession createSession() {
		Duration maxInactiveInterval = Duration
				.ofSeconds((this.defaultMaxInactiveInterval != null)
						? this.defaultMaxInactiveInterval
						: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
		RedisSession session = new RedisSession(maxInactiveInterval);
		session.flushImmediateIfNecessary();
		return session;
	}

好文参考:Spring-Session实现session共享原理及解析_五霸哥的博客-CSDN博客_session共享如何实现

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

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

相关文章

【产品设计】APP提升用户注册率的五个方案探讨结论

首先介绍下前置条件&#xff0c;我们在做产品时&#xff0c;出于一开始对行业特征和用户参与度的考虑&#xff0c;所以对注册没有要求&#xff0c;当用户触发需要记录用户行为的功能时才会弹出登陆注册的聚合页面&#xff0c;能够触发弹出的功能有&#xff1a;赞、写点评、回复…

ActiveMQ 笔记(九)Windows部署:单机与集群部署流程

1. 单机版部署 版本关系说明:JDK8环境只能安装5.2及以下的。超过5.2需要JDK9及以上版本环境。 (1)官网下载安装包 activemq.apache.org 选择一个版本进行下载, (2)解压安装包 下载完成之后解压 下载后直接解压缩直接就能用(免安装)。 bin/是服务启动相关的命令文件…

Xylan-PEG-NHS|木聚糖-聚乙二醇-琥珀酰亚胺

Xylan-PEG-NHS|木聚糖-聚乙二醇-琥珀酰亚胺 琥珀酰亚胺又称为丁酰亚胺或丁二酰亚胺&#xff0c;是一种无色针状结晶或具有淡褐色光泽的薄片固体&#xff0c;味甜。易溶于水、醇或氢氧化钠溶液&#xff0c;不溶于醚、氯仿等 中文名称&#xff1a;木聚糖-聚乙二醇-琥珀酰亚胺 …

目标检测 YOLOv5 开源代码项目-环境配置问题

学习视频&#xff1a; 目标检测 YOLOv5 开源代码项目调试与讲解实战【土堆 x 布尔艺数】 项目源码&#xff1a;ultralytics / yolov5 教程&#xff1a;手把手带你调参Yolo v5 (v6.2)&#xff08;推理&#xff09; 遇到的问题&解决的链接&#xff1a; ERROR: Failed buil…

怎样查看对方计算机网络密码,如何查看邻居家的wifi密码?不知道wifi密码怎么蹭网...

如何查看邻居家的wifi密码&#xff1f;不知道wifi密码怎么蹭网&#xff1f;不知道大家有没有遇到这样的问题&#xff1a;手机虽然已经连接到了WiFi&#xff0c;但是却已经忘记了WiFi密码。这时候如果会有一丝担忧在心中&#xff0c;如果下次连接不上&#xff0c;或是有人问你密…

[C语言数据结构]树

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录1.树1.1树的概念&#xff1a;1.2树与非树1.3一些关于树的概念&#xff1a;1.4树的一些表示方法&#xff1a;2.二叉树2.1概念&#xff1a;2.2二叉树的特点2.3满二叉树…

为什么在做微服务设计的时候一定需要DDD?

记得之前在规划和设计微服务架构的时候&#xff0c;张队长给了我一个至今依然记忆深刻的提示&#xff1a;你的设计蓝图里为什么没有看到DDD的影子呢&#xff1f; 随着对充血模型的领域认知的加深&#xff0c;我越加感觉到DDD的重要性。于是网上一顿海找&#xff0c;并做了学习…

Thinkpad x13 锐龙安装 Archlinux 记录

硬件配置&#xff1a; 笔记本影响cpu显卡内存硬盘ThinkPad X13 锐龙版r7 4750U核显16g1TB 山寨固态&#xff08;大华&#xff09;镜像准备 https://archlinux.org/download/ http://mirrors.163.com/archlinux/iso/2022.12.01/ 每次安装都检查iso镜像是否是网站最新的&#x…

国外数字书籍第2期

1、高级FPGA设计 (中英文版) 本书主要讲解了FPGA设计、方法和实现。这本书略去了不太必要的理论、推测未来的技术、过时工艺的细节&#xff0c;用简明、扼要的方式描述FPGA中的关键技术。 图1 高级FPGA设计 (克里兹)(英文版)主要内容包括&#xff1a;设计速度高、体积小、功耗低…

代码随想录算法训练营第五十三天|1143.最长公共子序列、1035.不相交的线、53. 最大子序和

LeetCode 1143.最长公共子序列 链接&#xff1a;1143.最长公共子序列 思路&#xff1a; 这题和上一题718. 最长重复子数组非常的像&#xff0c;唯一的区别就是这里的子序列可以不是连续的&#xff0c;既然不要求连续&#xff0c;我们可以在定义下标的时候不限制以特定字母结…

实战三十一:基于LightGCN推荐算法的推荐系统详细教程代码+数据

推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)构造出用户-项目交互图,协同过滤利用过去的用户-项目交互来实现预测;对相似的用户进行推荐相同喜好的item,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的…

web前端进阶<7>:3d图像翻转效果

这几天又闲着无聊&#xff0c;自己学的东西又有一点不牢固了&#xff0c;需要写一个小程序来巩固一下&#xff0c;相信大家也时有同感吧&#xff01;那么这一期就给大家带来一个炫酷的相册3d翻转效果&#xff0c;如果搭上炫酷的文字和动画那肯定是高端、霸气上档次的&#xff1…

安科瑞AF-GSM系列 智能物联网网关 支持4G/NB通讯-三防与水文监测

安科瑞 王晶淼/刘芳 1、商品描述 AF-GSM是安科瑞电气推出的新型的4G远程无线数据采集设备&#xff0c;采用嵌入式设计&#xff0c;内嵌TCP/IP协议栈&#xff0c;同时采用了功能强大的微处理芯片&#xff0c;配合内置看门狗&#xff0c;性能可靠稳定。 本产品提供标准RS485数…

Postman 调用 Spring Boot 文件上传接口

文章目录Postman 调用文件上传接口Postman 简介Spring Boot 定义文件上传的接口Postman 调用文件上传接口文件上传接口源码参考文献Postman 调用文件上传接口 Postman 简介 Postman 是一个用于构建和使用 API 的 API 平台。 Postman 简化了 API 生命周期的每一步&#xff0c;…

怎么把PDF加密?这3种方法让你轻松给PDF文件加密

现在我们的工作、学习和生活中&#xff0c;常会用到办公软件&#xff0c;其中PDF因为不易编辑&#xff0c;传输效果好&#xff0c;可以加密等特点深受大家的喜爱。我作为一个职场人&#xff0c;在需要分享PDF文件到人数比较多的平台时&#xff0c;为了不让其他人可以随意地查看…

Jnekins Active动态参数 集成Gitlab实践

参数化构建这里可以添加选项参数&#xff0c;一些字符串的参数&#xff0c;非常的多。这些参数在流水线执行的时候&#xff0c;它会加入到流水线里面去&#xff0c;最终在流水线运行的时候调用这些参数&#xff0c;选完参数之后再去构建。 写一段groovy脚本&#xff0c;然后返回…

二分查找算法【包括数组全局有序和局部有序的介绍,以及求局部最小值】

二分查找算法 二分查找要点&#xff1a;有序&#xff0c;但是一定全局有序吗&#xff1f;> 不一定需要全局有序 全局有序概念 一个有序的数组&#xff0c;通过找到 L 和 R 的中点值 &#xff0c;与目标值比较&#xff0c;来排除一半错误的信息 时间负责度计算 32 16 8 …

Codeforces Round #787 (Div. 3) F. Vlad and Unfinished Business

翻译&#xff1a; Vlad和Nastya住在一个由&#x1d45b;房子和&#x1d45b;−1路组成的城市。从每一个房子&#xff0c;你只需要沿着路走就可以到达另一个。也就是说&#xff0c;城市是一棵树。 弗拉德住在索引为&#x1d465;的房子里&#xff0c;娜斯提亚住在索引为&#…

[附源码]Python计算机毕业设计SSM交通事故记录信息管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

「Redis数据结构」字符串对象String

「Redis数据结构」字符串对象String 文章目录「Redis数据结构」字符串对象String一、概述二、编码分类intembstrrow三、小结四、参考一、概述 字符串数据类型是Redis里最常用的类型&#xff0c;它的键和值都是字符串&#xff0c;使用起来非常的方便。虽然字符串数据类型的值都…