Spring Security之Session管理

ops/2024/9/20 1:30:57/ 标签: spring, php, java

前言

在聊认证过滤器的时候,我们埋了个坑:Session管理。实际上,事情从这里开始,就变得复杂了。提前跟大家交个底:后续我们将涉及多个需要协同才能完成的功能。

什么是Session

想要管理session,就必须搞清楚Session的来龙去脉。

Session的产生

我们都知道HTTP协议本身是一种无状态的协议,即每个请求都是独立的,服务器不会记住前一个请求的信息。然而,在Web应用中,经常需要跟踪用户的会话状态,以便提供个性化的服务和保持用户数据的连续性。

为了弥补HTTP协议的无状态性,Session机制应运而生。Session是一种在客户端和服务器之间保持状态的机制,通过在服务器端存储用户的状态信息,并在每个请求中传递一个唯一的会话标识符(通常是JSESSIONID),使得服务器能够识别来自同一个客户端的多个请求,并为其提供个性化的响应。

在Web应用中,当客户端首次访问服务器时,服务器会为其创建一个新的Session对象,并生成一个与之关联的会话标识符。这个标识符会通过特定的方式(如cookies或URL重写)传递给客户端,并在后续的请求中由客户端发送回服务器。服务器根据会话标识符从Session池中检索出对应的Session对象,从而获取用户的会话状态信息。

总结一下:

  • Session是服务器用来跟踪用户状态的一种机制
  • 当用户访问Web应用时,服务器会为用户创建一个唯一的Session ID,并通过Cookie或其他方式将其发送给用户。
  • 在后续的请求中,用户会携带这个Session ID,服务器通过解析这个ID来识别用户,并恢复用户的会话状态。

Session的安全威胁

  1. 会话劫持(Session Hijacking)
    攻击者通过各种手段(如网络嗅探、跨站脚本攻击等)获取到用户的Session ID,然后利用这个有效的Session ID伪装成合法用户,访问用户的敏感信息或执行恶意操作。

  2. 会话固定(Session Fixation)
    攻击者预先在用户的目标服务器上创建一个有效的Session,然后通过各种手段诱使用户使用这个Session。这样,攻击者就可以在用户正常登录后接管用户的会话,获取用户的敏感信息。

  3. 跨站请求伪造(CSRF)
    攻击者诱导用户访问一个恶意网站,该网站包含指向目标网站的恶意请求,由于用户的浏览器会自动带上用户的Session信息,因此这个恶意请求会被视为用户的合法请求,从而导致用户的数据被篡改或泄露。

  4. 中间人攻击
    攻击者通过拦截客户端和服务器之间的通信,修改或窃取Session信息,从而获取用户的敏感数据或执行恶意操作。

Session管理

有了概念上的铺垫,结合《Spring Security之认证过滤器》,我们初步理解一下Session管理的作用。
而在Spring Security中,哪些功能才算呢?这得从SessionManagermentFilter的核心组件来看:SessionAuthenticationStrategy。

SessionAuthenticationStrategy

该组件的定位是:在认证(成功)时,为HttpSession相关行为提供插件式支持。

子类作用描述
AbstractSessionFixationProtectionStrategy固定会话防御策略用于防御固定会话攻击
CsrfAuthenticationStrategy跨域请求伪造防御策略为了确保认证前的csrfToken与认证后不一样,需要在认证成功后重置crsfToken。
RegisterSessionAuthenticationStrategy注册当前认证成功的Session可以用于追踪session,也可以统计当前在线session数量,乃至于协助控制某个用户同时在线数量
ConcurrentSessionControlAuthenticationStrategysession并发控制用于控制某个用户同时在线数量,需要搭配前者使用
  • AbstractSessionFixationProtectionStrategy
    他的核心逻辑主要是:

    如果不存在session,也就没有防御的必要。
    加同步锁后执行子类防御逻辑
    发布session事件

    具体策略有两个:

    ChangeSessionIdAuthenticationStrategy只修改sessionId,这也是默认策略
    SessionFixationProtectionStrategy把原session失效,再重建session,并拷贝原session中的属性

    前者的核心主要是调用:HttpSession#changeSessionId()方法。
    后者的核心则包括:让当前session失效-HttpSession#invalidate(); 重建session-request.getSession(true),然后拷贝将原session中的属性拷贝到新session中。

    实际上对于高版本的Tomcat(包括SpringBoot内置的,支持Servlet3.1及以上版本即可),二者在实际效果上并没有区别。因为前者改变了sessionId,内容不变。而后者重建了session,自然sessionId也是发生变化了的,又将原来session的相关属性复制过来。因此也相当于内容不变,只改了sessionId。

  • CsrfAuthenticationStrategy
    很明显,这与跨域请求有关。不难猜测,跨域请求防御必定是个需要多个Filter协作的过滤器。但这里我们只聊他在这里的作用,聊到跨域请求防御再把这块串起来。

    java">public class CsrfAuthenticationStrategy implements SessionAuthenticationStrategy {@Overridepublic void onAuthentication(Authentication authentication, HttpServletRequest request,HttpServletResponse response) throws SessionAuthenticationException {boolean containsToken = this.csrfTokenRepository.loadToken(request) != null;// 存在csrfToken则重置if (containsToken) {// 先清空:存入nullthis.csrfTokenRepository.saveToken(null, request, response);// 再重新创建一个csrfTokenCsrfToken newToken = this.csrfTokenRepository.generateToken(request);// 保存到csrfToken仓库this.csrfTokenRepository.saveToken(newToken, request, response);// 放入request中,以便可能的页面渲染。request.setAttribute(CsrfToken.class.getName(), newToken);request.setAttribute(newToken.getParameterName(), newToken);}}
    }
    

    PS: CsrfTokenRepository是跨域防御的核心组件,跳过。

  • RegisterSessionAuthenticationStrategy
    这个主要靠SessionRegistry来实现。而SessionRegistry也是后面session并发控制的关键。因此他也是个协同组件哦。只不过不是在Filter层面,而是跟ConcurrentSessionControlAuthenticationStrategy协同。前者,用于登记注册,后者则利用登记的session进行统计,并以此决定后续操作。

    java">public interface SessionRegistry {// 获取所有(在线的/离线但尚未清理的-session层面的)用户List<Object> getAllPrincipals();// 获取所有session(支持查询已超时的session)List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions);// 根据sessionId获取Session信息SessionInformation getSessionInformation(String sessionId);// 根据sessionId刷新上一次请求,这与自定义的SessionInformation有关。void refreshLastRequest(String sessionId);// 注册新的sessionvoid registerNewSession(String sessionId, Object principal);// 根据sessionId移除SessionInformation void removeSessionInformation(String sessionId);
    }
    

    RegisterSessionAuthenticationStrategy自然使用的是registerNewSession方法进行登记。

  • ConcurrentSessionControlAuthenticationStrategy

    java">public class ConcurrentSessionControlAuthenticationStrategyimplements MessageSourceAware, SessionAuthenticationStrategy {@Overridepublic void onAuthentication(Authentication authentication, HttpServletRequest request,HttpServletResponse response) {// 获取当前用户的最大session限制。这里是配置好的,不是动态变化的,除非定制。int allowedSessions = getMaximumSessionsForThisUser(authentication);if (allowedSessions == -1) {// -1 则表示不限制,因此直接放行。return;}// 需要限制用户的session个数List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);int sessionCount = sessions.size();if (sessionCount < allowedSessions) {// 尚未达到多点登录的限制,允许登录return;}if (sessionCount == allowedSessions) {HttpSession session = request.getSession(false);if (session != null) {// 达到限制,但属于是当前已在线的session,是正常请求。for (SessionInformation si : sessions) {if (si.getSessionId().equals(session.getId())) {return;}}}}// 超出限制,且又来了新的sessionallowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);}protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,SessionRegistry registry) throws SessionAuthenticationException {if (this.exceptionIfMaximumExceeded || (sessions == null)) {// 需要以异常的形式抛出throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",new Object[] { allowableSessions }, "Maximum sessions of {0} for this principal exceeded"));}// 通过lru的方式,得到最近使用时间最远的session,并使其失效sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);for (SessionInformation session : sessionsToBeExpired) {session.expireNow();}
    }
    

问题延伸

现在我们知道SessionAuthenticationStrategy的作用,也知道他可以实现哪些所谓的“与Session相关的操作”了。在这里,我要问大家一个问题:

如果我们不用SessionAuthenticationStrategy能正常登录吗?

很显然,我们依然能够正常登录,只不过与之相关的固定会话防御、跨域防御、并发会话控制功能无法实现。

SessionManagementFilter

我们先看看类注释:

检测在请求开始开始时用户已经是否已经认证过了,如果已认证,则调用配置好的SessionAuthenticationStrategy来完成session相关的活动,例如:固定会话保护机制和多请求并发登录检查。

与session相关的活动,我认为可以分为两类:

  • 与session相关的安全防御活动。例如:固定会话保护机制、跨域请求伪造防御
  • 与session相关的功能。例如:session并发控制(多点登录)

以上行为/活动,无疑被封装到SessionAuthenticationStrategy的实现之中。

实现原理

SessionManagementFilter
可以看出,条件很多,核心功能有两点:一是session超时策略。二是SessionAuthenticationStrategy、SecurityContextRepository两个组件的执行。

小结

SessionManagementFilter会关注session超时,当session超时时会自动跳转到指定的页面。而其本身设计的重点是,执行SessionAuthenticationStrategy的相关策略。至于SecurityContextRepository,这个与BUG
SEC-1396 有关。

与认证过滤器的协同

准确来说,应该说是与部分认证过滤器的协同。因为我们的UsernamePasswordAuthenticationFilter,或者说任何实现了AbstractAuthenticationProcessingFilter,其本身就会执行SessionAuthenticationStrategy

对于UsernamePasswordAuthenticationFilter,个人认为只有在并发请求、session超时管理情况下才能派上用场。如果不使用SessionManagementFilter的话,我们要配置SessionAuthenticationStrategy可以自己new相应的策略在放到HttpSecurity的共享对象中就行。但session超时的请求如果想要处理,例如跳转登录页面,就得我们自己处理了。

而像AbstractPreAuthenticatedProcessingFilterRememberMeAuthenticationFilterBasicAuthenticationFilter等,就不会执行这个。甚至,也不会执行SecurityContextRepository,因此就必须要SessionManagementFilter的协助。至于为什么UsernamePasswordAuthenticationFilter这么特别,我只能猜测他可能是先实现的,而后者是后面设计的。

SessionManagementConfigurer

这是负责配置session管理配置器。发布的过滤器除了SessionManagementFilter,还有ConcurrentSessionFilter。这也是SessionManagement容易令人迷糊的地方,明明名字都一样,却不是一一对应。

java">public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {@Overridepublic void init(H http) {SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);boolean stateless = isStateless();if (securityContextRepository == null) {// 设置SecurityContextRepositoryif (stateless) {// 无状态应用设置为NullSecurityContextRepositoryhttp.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());}else {// 默认为HttpSessionSecurityContextRepositoryHttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);if (trustResolver != null) {// 确保httpSecurityRepository使用的是共享对象trustResolverhttpSecurityRepository.setTrustResolver(trustResolver);}http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);}}RequestCache requestCache = http.getSharedObject(RequestCache.class);if (requestCache == null) {if (stateless) {// 无状态应用设置NullRequestCachehttp.setSharedObject(RequestCache.class, new NullRequestCache());}}// SessionAuthenticationStrategy设置为共享对象http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));// InvalidSessionStrategy设置为共享对象http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());}@Overridepublic void configure(H http) {// 创建SessionManagementFilter SessionManagementFilter sessionManagementFilter = createSessionManagementFilter(http);if (sessionManagementFilter != null) {http.addFilter(sessionManagementFilter);}// 开启session并发控制,需要创建ConcurrentSessionFilter if (isConcurrentSessionControlEnabled()) {ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);concurrentSessionFilter = postProcess(concurrentSessionFilter);http.addFilter(concurrentSessionFilter);}if (!this.enableSessionUrlRewriting) {// 禁止url编码,这个涉及到重定向-其实就是封装一下请求http.addFilter(new DisableEncodeUrlFilter());}if (this.sessionPolicy == SessionCreationPolicy.ALWAYS) {// 确保session存在。就是在请求进入的第一时间,获取一下session。http.addFilter(new ForceEagerSessionCreationFilter());}}private SessionManagementFilter createSessionManagementFilter(H http) {if (shouldRequireExplicitAuthenticationStrategy()) {return null;}// 对于stateful应用直接使用HttpSessionSecurityContextRepositorySecurityContextRepository securityContextRepository = this.sessionManagementSecurityContextRepository;// 获取配置的SessionAuthenticationStrategy,创建SessionManagementFilter// 总的来说,SessionManagementConfigurer的重中之重就是配置SessionAuthenticationStrategy// 这也是SessionManagementFilter的核心逻辑所在SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository,getSessionAuthenticationStrategy(http));if (this.sessionAuthenticationErrorUrl != null) {sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(this.sessionAuthenticationErrorUrl));}InvalidSessionStrategy strategy = getInvalidSessionStrategy();if (strategy != null) {sessionManagementFilter.setInvalidSessionStrategy(strategy);}AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();if (failureHandler != null) {sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);}AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);if (trustResolver != null) {sessionManagementFilter.setTrustResolver(trustResolver);}sessionManagementFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());return postProcess(sessionManagementFilter);}private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {if (this.sessionAuthenticationStrategy != null) {// 避免重复创建return this.sessionAuthenticationStrategy;}List<SessionAuthenticationStrategy> delegateStrategies = this.sessionAuthenticationStrategies;SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;if (this.providedSessionAuthenticationStrategy == null) {// 用户没有配置SessionAuthenticationStrategy默认为sessionFixationAuthenticationStrategydefaultSessionAuthenticationStrategy = postProcess(this.sessionFixationAuthenticationStrategy);}else {// 设置为用户指定策略defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy;}// 是否开启并发控制if (isConcurrentSessionControlEnabled()) {SessionRegistry sessionRegistry = getSessionRegistry(http);ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);registerSessionStrategy = postProcess(registerSessionStrategy);delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy,defaultSessionAuthenticationStrategy, registerSessionStrategy));}else {delegateStrategies.add(defaultSessionAuthenticationStrategy);}// 将策略通过CompositeSessionAuthenticationStrategy组装this.sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));return this.sessionAuthenticationStrategy;}}

这里主要两个方法:
init方法
该方法可能会创建共享对象:

共享对象释义stateless场景非stateless场景
SecurityContextRepository安全上下文仓库,一般将安全上下文保存在session中设置RequestAttributeSecurityContextRepository为共享对象,而sessionManagementFilter使用NullSecurityContextRepositoryHttpSessionSecurityContextRepository,共享对象与sessionManagementFilter使用的是同一个对象
RequestCache请求缓存,与认证成功后,跳转上一次请求有关。创建NullRequestCache
SessionAuthenticationStrategysession认证策略与session相关的活动
InvalidSessionStrategysession超时策略session超时后的处理

从这里也能看出,session管理必须要识别应用是否为stateless应用。因为对于stateless应用,是没有会话概念的。自然session管理也就没有这个必要了。stateless是通过token来维持类似于会话的功能的,这个token可能是存在于header也可能存在于RequestAttribute(默认选择)中。这也就解释了SecurityContextRepository的创建。

  • configure方法
    这个方法最主要的是创建SessionManagementFilter,而创建SessionManagementFilter的核心是创建SessionAuthenticationStrategy。
    第一层是session的创建策略,前面仔细聊过,不多逻辑。如果没有指定,则会使用修改sessionId的策略。第二层是session并发控制。这个会涉及SessionRegistry,会优先从httpSecurity的共享对象中获取,如果没有就创建一个,同时注册为监听器。没错,他就是利用监听机制完成session的注册和注销的。然后将SessionRegistry封装成RegisterSessionAuthenticationStrategy;构建好所有的SessionAuthenticationStrategy之后,就通过组合模式,封装成CompositeSessionAuthenticationStrategy统一调用。
    除了以上的核心逻辑外,SessionManagementFilter还有session超时处理所涉及的:超时处理器或者超时处理策略;SessionAuthenticationStrategy处理失败的处理器。以及与判断是否需要执行sessionManage逻辑的组件:SecurityContextHolderStrategy、TrustResolver、SecurityContextRepository。
    对于SessionManagementConfigurer来说,还有ConcurrentSessionFilter、DisableEncodeUrlFilter、ForceEagerSessionCreationFilter。第一个不多说,第二个是禁止重定向的url编码的,第三个是为了确保session存在。

总结

  1. SessionManagementFilter的核心是SessionAuthenticationStrategy,主要涉及的主要有两个功能:Session的创建策略,Session的并发控制。
  2. SessionManagementConfigurer配置session并发控制时,会额外引入一个ConcurrentSessionFilter,用于控制session数量。由此可见,并发控制是我们遇到的第一个需要多个过滤器协作完成的功能
  3. SessionManagementFilter除了管理SessionAuthenticationStrategy之外,还有负责session超时的处理。
  4. SessionManagementFilter可以是UsernamePasswordAuthenticationFilter的补充。一来可以应付session超时,二来在发生并发请求时,可以通过SessionAuthenticationStrategy执行session相关操作。如果是其他登录方式,他就是名副其实的session管理器,离了他session相关的都不会执行。

下期预告:登录了、看了session了,下一个就是认证信息的存储。

PS:我们的花了大部分篇幅都在聊组件、配置。像SessionManagementFilter、ConcurrentSessionFilter的源码我们都没有仔细分析,原因是其核心逻辑比较简单,大家可以自行阅读。我们重点聊的还是组件功能和配置,这是spring security的学习起来比较困难的地方。

参考

Authentication Persistence and Session Management
Spring Security 如何防止 Session Fixation 攻击


http://www.ppmy.cn/ops/7798.html

相关文章

Web前端框架/库/工具

前言 前端从步枪&#xff08;原生js&#xff09;到了半自动武器&#xff08;jQuery&#xff09;并进化为全自动武器&#xff08;三大框架&#xff08;angular&#xff0c;react&#xff0c;vue及其生态链&#xff09;&#xff09;。 常说工欲善其事必先利其器。对于那些想要提…

TPCC MySQL

目录 1. tpcc-mysql下载 2. tpcc-mysql安装 3. 初始化tpcc-mysql 4. 使用tpcc-mysql测试 5. 结果解释 1. tpcc-mysql下载 https://github.com/Percona-Lab/tpcc-mysql 2. tpcc-mysql安装 [rootlocalhost soft]# unzip tpcc-mysql-master.zip [rootlocalhost soft]# cd t…

代码随想录训练营24day-贪心算法2

一、122 买卖股票最佳时机 题目介绍限制条件&#xff0c;必须卖了再买&#xff0c;而且当前交易一只股票。一开始想法是去遍历&#xff0c;找到每个区间段间的差值&#xff0c;然后再相加。看了解答&#xff0c;其实每一天的利润&#xff0c;都是可以用差值表示出来&#xff0…

Spring - 1 ( 8000 字 Spring 入门级教程 )

一&#xff1a;SpringBoot 快速上手 环境准备 ⾃检Idea版本: 社区版: 2021.1 -2022.1.4专业版: ⽆要求 如果个⼈电脑安装的idea不在这个范围, 需要卸载重新安装.&#xff08;⼀定要删除注册表&#xff09; Maven Maven是⼀个项⽬管理⼯具。基于POM(Project Object Model,…

centos 7.9 安装 ftp 传输文件

ftp server 端 sudo yum install vsftpd ftp其中 vsftpd 为 ftp server 端&#xff0c;ftp 包含 ftp 这个客户端命令。 # sudo rpm -ql vsftpd/etc/logrotate.d/vsftpd /etc/pam.d/vsftpd /etc/vsftpd /etc/vsftpd/ftpusers /etc/vsftpd/user_list /etc/vsftpd/vsftpd.conf /…

耀斑层-如何在Unity中实现耀斑亮光效果

在Unity中实现耀斑亮光效果可以通过以下步骤来实现&#xff1a; 创建一个空物体作为光源&#xff1a;在场景中创建一个空物体&#xff0c;并将其放置在需要发出耀斑亮光效果的位置上。 添加光源组件&#xff1a;选中空物体&#xff0c;在Inspector面板中点击"Add Compone…

C# 下记录(Record)详解

在C# 9.0中&#xff0c;引入了一个新的关键字&#xff1a;record。record关键字用于定义记录类型&#xff0c;这是一种不可变的数据结构&#xff0c;用于表示具有明确字段名称和类型的数据集。本文将详细介绍C#中record类型的使用和特点&#xff0c;以及如何通过记录记录器&…

使用easyexcel将csv转为excel

一.背景 供应商系统下载的csv文件不支持域控&#xff08;主要是第三方wps服务不能对csv文件加密&#xff0c;但是可以对office系列产品进行权限访问的加密控制&#xff09;。因此思路就改为现将csv文件转为excel文件&#xff0c;然后对excel文件进行加域控制。本文主要介绍如何…

数据结构--双向链表

在讲双向链表之前&#xff0c;我们先了解一下链表的分类&#xff1a; 链表的结构⾮常多样&#xff0c;主要分为带头与不带头、单向与双向、循环与不循环。三个种类可以任意搭配&#xff0c;所以总共可以形成八种链表&#xff0c;但是最常用的是单向不带头不循环链表和双向带头循…

html、css、QQ音乐移动端静态页面,资源免费分享,可作为参考,提供InsCode在线运行演示

CSDN将我上传的免费资源私自变成VIP专享资源&#xff0c;且作为作者的我不可修改为免费资源&#xff0c;不可删除&#xff0c;寻找客服无果&#xff0c;很愤怒&#xff0c;&#xff08;我发布免费资源就是希望大家能免费一起用、一起学习&#xff09;&#xff0c;接下来继续寻找…

代码托管基础操作

在待上传代码文件夹中右键&#xff0c;打开Git Bash Here依次输入以下命令&#xff1a; git init(在本地初始化一个代码仓库&#xff0c;具体表现为会在你的文件夹里出现一个隐藏的.git文件夹) git add .&#xff08;先把代码放到本地的一个缓冲区&#xff09;添加当前目录下的…

命理八字之答案之书前端uniapp效果实现

#uniapp# #答案之书# 不讲废话&#xff0c;先上截图 <div class"padding"><div class"flex align-center justify-center" style"padding-top:100px;"><div class"radarContainer"><div id"radarBox"…

初识ansible变量及实例配置

目录 1、为什么要使用变量 2、变量分类 3、 变量详解 3.1 vars,vars_files , group_vars 3.1 .1 vars 剧本中定义变量 3.1.2 vars_file 将变量存放到一个文件中&#xff0c;并在剧本中引用 3.1.3 group_vars 创建一个变量文件给某个组使用 实例1-根据不同的主机…

[ LeetCode ] 题刷刷(Python)-第35题:搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 nums 为 无重复元素 的 升序 排列数组 请必须使用时间复杂度为 O(log n) 的算法。 示例 示例 1: 输入: …

Django老项目升级到新版本

手上有个 Django 老项目&#xff0c;一直跑得好好的&#xff0c;好几年没动过了&#xff0c;维护费收得正爽&#xff0c;没想到客户来了个新的运营人员&#xff0c;丢了个改动需求过来。我一看也没啥大改&#xff0c;就答应了。大意了。 问题 刚开始改&#xff0c;我这种老鸟…

MongoDB聚合运算符:$sampleRate

MongoDB聚合运算符&#xff1a;$sampleRate 文章目录 MongoDB聚合运算符&#xff1a;$sampleRate语法使用举例 $sampleRate聚合运算符用$match&#xff0c;按照指定的抽样比例&#xff0c;从输入的文档中随机选择相应的文档。 语法 { $sampleRate: <non-negative float>…

使用Spring Boot整合定时任务(Schedule)

1、添加依赖&#xff1a; 在pom.xml文件中添加Spring Boot的定时任务依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId> </dependency> 2、创建定时任务类&#xff1a; 创建…

搜索+剪枝,LeetCode 216. 组合总和 III

目录 一、题目 1、题目描述 2、接口描述 python3 cpp 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 python3 cpp 一、题目 1、题目描述 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多…

Linux下跟踪某个进程的内核处理时延消耗情况

1.利用系统自动的trace功能&#xff0c;编辑如下脚本&#xff0c;vim trace_process.sh #!/bin/sh cd /sys/kernel/debug/tracing/ #清空原有跟踪信息 echo > trace echo nop > current_tracer #设置要跟踪的进程 echo "pid281255" echo 281255 > set_ftra…

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(TcpServer板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现⾼并发服务器&#xff08;TcpServer板块&#xff09; 一、思路图二、模式关系图三、定时器的设计1、Linux本身给我们的定时器2、我们自己实现的定时器&#xff08;1&#xff09;代码部分&#xff08;2&#xff09;思…