Spring Security之Session管理

news2024/7/31 6:21:46

前言

在聊认证过滤器的时候,我们埋了个坑: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协作的过滤器。但这里我们只聊他在这里的作用,聊到跨域请求防御再把这块串起来。

    public class CsrfAuthenticationStrategy implements SessionAuthenticationStrategy {
    	@Override
    	public void onAuthentication(Authentication authentication, HttpServletRequest request,
    			HttpServletResponse response) throws SessionAuthenticationException {
    		boolean containsToken = this.csrfTokenRepository.loadToken(request) != null;
    		// 存在csrfToken则重置
    		if (containsToken) {
    			// 先清空:存入null
    			this.csrfTokenRepository.saveToken(null, request, response);
    			// 再重新创建一个csrfToken
    			CsrfToken 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进行统计,并以此决定后续操作。

    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);
    	// 注册新的session
        void registerNewSession(String sessionId, Object principal);
    	// 根据sessionId移除SessionInformation 
        void removeSessionInformation(String sessionId);
    }
    

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

  • ConcurrentSessionControlAuthenticationStrategy

    public class ConcurrentSessionControlAuthenticationStrategy
    		implements MessageSourceAware, SessionAuthenticationStrategy {
    	@Override
    	public 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;
    					}
    				}
    			}
    		}
    		// 超出限制,且又来了新的session
    		allowableSessionsExceeded(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容易令人迷糊的地方,明明名字都一样,却不是一一对应。

public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {

	@Override
	public void init(H http) {
		SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
		boolean stateless = isStateless();
		if (securityContextRepository == null) {
			// 设置SecurityContextRepository
			if (stateless) {
				// 无状态应用设置为NullSecurityContextRepository
				http.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
			}
			else {
				// 默认为HttpSessionSecurityContextRepository
				HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
				httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
				httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
				AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
				if (trustResolver != null) {
					// 确保httpSecurityRepository使用的是共享对象trustResolver
					httpSecurityRepository.setTrustResolver(trustResolver);
				}
				http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
			}
		}
		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache == null) {
			if (stateless) {
				// 无状态应用设置NullRequestCache
				http.setSharedObject(RequestCache.class, new NullRequestCache());
			}
		}
		// SessionAuthenticationStrategy设置为共享对象
		http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
		// InvalidSessionStrategy设置为共享对象
		http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
	}

	@Override
	public 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应用直接使用HttpSessionSecurityContextRepository
		SecurityContextRepository 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默认为sessionFixationAuthenticationStrategy
			defaultSessionAuthenticationStrategy = 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.coloradmin.cn/o/1613158.html

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

相关文章

分析和比较深度学习框架 PyTorch 和 Tensorflow

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 深度学习作为人工智能的一个重要分支&#xff0c;在过去十年中取得了显著的进展。PyTorch 和 TensorFlow 是目前最受欢迎、最强大的两个深度学习框架&#xff0c;它们各自拥有独特的特点和优势。 1. Py…

Llama 3大模型发布!快速体验推理及微调

Meta&#xff0c;一家全球知名的科技和社交媒体巨头&#xff0c;在其官方网站上正式宣布了一款开源的大型预训练语言模型——Llama-3。 据了解&#xff0c;Llama-3模型提供了两种不同参数规模的版本&#xff0c;分别是80亿参数和700亿参数。这两种版本分别针对基础的预训练任务…

【系统分析师】软件工程

文章目录 1、信息系统生命周期2、软件开发模型2.1 原型及其演化2.2 增量模型和螺旋模型2.3 V模型、喷泉模型、快速应用开发2.4 构件组装模型2.5 统一过程-UP2.6 敏捷方法 3、逆向工程4、净室软件工程 【写在前面】 记录了一系列【系统分析师】文章&#xff0c;点击下面的链接&a…

【Kyuubi】Apache Kyuubi 1.8 特性解读

Apache Kyuubi 1.8 特性解读 1.Apache Kyuubi 简介2.场景扩展 —— 在线分析&#xff0c;离线跑批3.流式增强 —— 流批一体&#xff0c;面向未来4.企业特性 —— 行业沉淀&#xff0c;持续打磨5.开源社区 —— 开放包容&#xff0c;合作共赢 本文来自于 Apache Kyuubi PMC Mem…

数据结构与算法解题-20240421

数据结构与算法解题-20240421 一、278. 第一个错误的版本二、541. 反转字符串 II三、右旋字符串四、替换数字五、977.有序数组的平方 一、278. 第一个错误的版本 简单 你是产品经理&#xff0c;目前正在带领一个团队开发新的产品。不幸的是&#xff0c;你的产品的最新版本没有…

深度学习-优化策略

1.使用众所周知的梯度下降法。 &#xff08;1&#xff09;.批量梯度下降法&#xff1a;每次参数更新使用所有的样本&#xff08;2&#xff09;.随机梯度下降法&#xff1a;每次参数更新只使用一次样本&#xff08;3&#xff09;.小批量梯度下降法&#xff1a;每次参数更新使用…

【python】启动一个公司级项目的完整报错和解决方案

启动一个项目对于新手都是不容易的事情 操作 打开项目 使用pyCharm打开python项目以后&#xff0c;先找main方法&#xff0c;一般在根目录有一个.py的文件 点进去以后会让你配置Python解释器 每个项目都有自己的一个虚拟环境&#xff0c;配置自己的解释器&#xff0c;可能…

Gitea 简单介绍、用法以及使用注意事项!

Gitea 是一个轻量级的代码托管解决方案&#xff0c;它提供了一个简单而强大的平台&#xff0c;用于托管和协作开发项目。基于 Go 语言编写&#xff0c;与 GitLab 和 GitHub Enterprise 类似&#xff0c;但专为自托管而设计。以下是对 Gitea 的详细介绍&#xff0c;包括常用命令…

【语音识别】在Win11使用Docker部署FunASR服务器

文章目录 在 Win11 使用 Docker 部署 FunASR 服务器镜像启动服务端启动监控服务端日志下载测试案例使用测试案例打开基于 HTML 的案例连接ASR服务端 关闭FunASR服务 在 Win11 使用 Docker 部署 FunASR 服务器 该文章因官网文档不详细故写的经验论 官网文章&#xff1a;https:/…

Wpf 使用 Prism 实战开发Day21

配置默认首页 当应用程序启动时&#xff0c;默认显示首页 一.实现思路&#xff0c;通过自定义接口来配置应用程序加载完成时&#xff0c;设置默认显示页 步骤1.创建自定义 IConfigureService 接口 namespace MyToDo.Common {/// <summary>/// 配置默认显示页接口/// <…

Android Studio Emulator一直卡在Google Logo出不来

我尝试在androidstudio模拟器中运行我的应用程序&#xff0c;模拟器已经打开&#xff0c;但一直停留在Google徽标加载界面半个小时了都退不出来&#xff0c;也进不去。 解决方案如下&#xff1a; 我们创建模拟设备的时候&#xff0c;界面上会显示&#xff1a; 1.不要使用带有…

贝叶斯分类 python

贝叶斯分类 python 贝叶斯分类器是一种基于贝叶斯定理的分类方法&#xff0c;常用于文本分类、垃圾邮件过滤等领域。 在Python中&#xff0c;我们可以使用scikit-learn库来实现贝叶斯分类器。 下面是一个使用Gaussian Naive Bayes(高斯朴素贝叶斯)分类器的简单示例&#xff1…

使用Python进行云计算:AWS、Azure、和Google Cloud的比较

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行云计算&#xff1a;AWS、Azure、和Google Cloud的比较 随着云计算的普及&am…

Linux-软件安装--jdk安装

jdk安装 前言1、软件安装方式二进制发布包安装rpm安装yum安装源码编译安装 2、安装jdk2.1、使用finalShell自带的上传工具将jdk的二进制发布包上传到Linux2.2、解压安装包2.3、配置环境变量![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/61ba9750e2e34638a39575c5…

Spring-基于xml自动装配

版本 Spring Framework 6.0.9​ 1. 定义 Spring IoC容器在无需显式定义每个依赖关系的情况下&#xff0c;根据指定的策略&#xff0c;自动为指定的bean中所依赖的类类型或接口类型属性赋值。 2. 关键配置元素 BeanDefinitionParserDelegate类定义了autowire属性的属性值&…

打破国外垄断|暴雨发布纯血国产电脑

要说现在国产手机这边已然进入纯自研模式&#xff0c;但电脑这边却还是仍未打破国外技术垄断。但就在刚刚&#xff0c;暴雨发布自研架构台式机open Station X &#xff0c;这是纯血鸿蒙系统之后国产又一款纯血产品发布&#xff01;标志的我们已经彻底打破西方在硬件及软件方面的…

编译一个基于debian/ubuntu,centos,arhlinux第三方系统的问题解答

如果是开机卡boot注意看前面几行会有错误提示&#xff0c;一般会比较好找&#xff0c;下面是过了kernel内核加载后出现的问题 目录 上一篇文章 第一个问题 错误原因 解决办法 第二个问题 注意 第三个问题 上一篇文章 编译一个基于debian/ubuntu,centos,arhlinux第三方系…

垃圾收集器ParNewCMS与底层三色标记算法详解

垃圾收集算法 分代收集理论 当前虚拟机的垃圾收集都是采用分代收集算法,这种算法没有什么新思想,只是依据对象的存活周期不同将内存分为几块.一般将Java堆分为新生代和老年代,这样就可以根据各个年代的特点选择合适的垃圾收集算法. 比如在新生代中,每次收集都会有大量对象(近…

【Linux开发 第九篇】磁盘分区

Linux磁盘分区 磁盘分区 Linux分区是用来组成整个文件系统的一部分 Linux采用了一种叫载入的处理方法&#xff0c;它的整个文件系统中包括了一整套的文件和目录&#xff0c;且将一个分区和一个目录联系起来&#xff0c;这时要载入的一个分区将使它的存储空间开一个目录下获得…

基于spark进行数据分析的心力衰竭可视化大屏项目

基于spark进行数据分析的心力衰竭可视化大屏项目 项目背景 在当今的医疗领域&#xff0c;数据驱动的决策变得日益重要。心力衰竭作为常见的心血管疾病&#xff0c;其临床数据的分析对于改善患者治疗结果至关重要。本文将介绍如何利用Apache Spark进行大规模心力衰竭临床数据的…