Spring Security 6.x 系列(5)—— Servlet 认证体系结构介绍

news2024/9/29 7:09:15

一、前言

本章主要学习Spring Security中基于Servlet 的认证体系结构,为后续认证执行流程源码分析打好基础。

二、身份认证机制

Spring Security提供个多种认证方式登录系统,包括:

  • Username and Password:使用用户名/密码 方式进行认证登录
  • OAuth 2.0 Login:使用OpenID ConnectOAuth 2.0 方式进行认证登录
  • CAS: 使用CAS企业级单点登录系统 方式进行认证登录
  • SAML 2.0 Login:使用SAML 2.0 方式进行认证登录
  • Remember Me:使用记住我 方式进行认证登录
  • JAAS: 使用JAAS 方式进行认证登录
  • Pre-Authentication Scenarios:使用外部机制 方式进行认证登录
  • X509:使用X509 方式进行认证登录

三、认证组件简介

Spring Security中的认证相关组件:

  • SecurityContextHolder:安全上下文持有者,存储当前认证用户的SecurityContext

  • SecurityContext :安全上下文,包含当期认证用户的Authentication(认证信息),从SecurityContextHolder中获取。

  • Authentication :认证信息,用户提供的用于身份认证的凭据的输入。

  • GrantedAuthority :授予用户的权限(角色、作用域)。

  • AuthenticationManager :认证管理器,是一个接口,定义Spring Security过滤器执行身份认证的API

  • ProviderManager :提供者管理器,是AuthenticationManager的默认实现。

  • AuthenticationProvider : 认证提供者,由ProviderManager选择,用于执行特定类型的身份认证。

  • AuthenticationEntryPoint : 认证入口点,处理认证过程中的认证异常,比如:重定向登录页面

  • AbstractAuthenticationProcessingFilter :抽象认证处理过滤器,一个Filter抽象类,是身份验证的基础。

四、认证组件源码分析

4.1 SecurityContextHolder

SecurityContextHolder(安全上下文持有者)是Spring Security身份认证模型的核心,存储已认证用户详细信息,包含了SecurityContext(安全上下文):

在这里插入图片描述
当用户认证成功后,会将SecurityContext设置到SecurityContextHolder中,后续流程可以用过SecurityContextHolder静态方法直接获取用户信息:

SecurityContext context = SecurityContextHolder.getContext();// 获取 SecurityContext
Authentication authentication = context.getAuthentication();// 获取认证信息
String username = authentication.getName(); // 用户名
Object principal = authentication.getPrincipal(); // 当前用户的信息,通常是UserDetails的实例
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();// 权限

默认策略下,SecurityContextHolder使用ThreadLocal来存储信息,一个已认证的请求线程在过滤器阶段,会获取会话中的认证信息,并保存在当前线程的ThreadLocal中,方便业务代码获取用户信息,线程执行完毕后,FilterChainProxy会自动执行清除。

某些应用程序并不完全适合使用默认策略 ,因为它们使用线程有特定方式。 例如:Swing 客户端可能希望Java中的所有线程都使用相同的SecurityContextHolder,您可以在启动时使用策略进行配置,以指定您希望如何存储SecurityContext

其他应用程序可能希望由安全线程派生的线程也采用相同的安全标识。 您可以通过两种方式更改默认模式:

  • 第一种是设置系统属性,通过System.getProperty("spring.security.strategy")读取设置系统属性。
  • 第二种是在调用静态方法,通过调用SecurityContextHolder.setStrategyName(String strategyName)设置。

SecurityContextHolder提供的strategyName有以下几种:

  • SecurityContextHolder.MODE_GLOBAL
  • SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
  • SecurityContextHolder.MODE_THREADLOCAL

大多数应用程序不需要更改默认值。

4.2 SecurityContex

SecurityContex是一个接口,从SecurityContextHolder中获取,包含了Authentication (认证信息),提供了两个简单方法:

public interface SecurityContext extends Serializable {

	/**
	 * 获取当前已通过身份验证的主体或身份验证请求令牌。
	 */
	Authentication getAuthentication();

	/**
	 * 更改当前已通过身份验证的主体或删除身份验证信息
	 */
	void setAuthentication(Authentication authentication);

}

SecurityContex的实现类SecurityContextImpl也很简单:

public class SecurityContextImpl implements SecurityContext {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private Authentication authentication;

	public SecurityContextImpl() {
	}

	public SecurityContextImpl(Authentication authentication) {
		this.authentication = authentication;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof SecurityContextImpl) {
			SecurityContextImpl other = (SecurityContextImpl) obj;
			if ((this.getAuthentication() == null) && (other.getAuthentication() == null)) {
				return true;
			}
			if ((this.getAuthentication() != null) && (other.getAuthentication() != null)
					&& this.getAuthentication().equals(other.getAuthentication())) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Authentication getAuthentication() {
		return this.authentication;
	}

	@Override
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.authentication);
	}

	@Override
	public void setAuthentication(Authentication authentication) {
		this.authentication = authentication;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(getClass().getSimpleName()).append(" [");
		if (this.authentication == null) {
			sb.append("Null authentication");
		}
		else {
			sb.append("Authentication=").append(this.authentication);
		}
		sb.append("]");
		return sb.toString();
	}

}

4.3 Authentication

Authentication是一个接口,在Spring Security中主要有两个作用:

  • 预认证用户信息:此时没有经过认证,作为AuthenticationManager(认证管理器)的一个输入参数,用于提供认证凭证,在此时.isAuthenticated()值为false
  • 当前认证的用户:表示当前经过身份认证的用户,可以从SecurityContext获取当前的Authentication(认证信息)

Authentication包含以下信息:

  • principal:用户主体标识。 使用用户名/密码进行身份验证时,这通常是UserDetails的用户名。

  • credentials:通常是密码。 在许多情况下,在用户通过身份验证后会清除此信息,以确保它不会泄露。

  • authoritiesGrantedAuthority实例是授予用户的高级权限。 下文详细介绍。

Authentication源码如下所示:

public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

Authentication接口有很多实现类,对应不同的认证方式。比如:使用用户名/密码进行身份验证时,使用的是UsernamePasswordAuthenticationToken

在这里插入图片描述

4.4 GrantedAuthority

GrantedAuthority实例是授予用户的权限,包括:角色、作用域。可以从Authentication.getAuthorities() 方法获取已认证用户的权限集合。

GrantedAuthority源码如下所示:

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

只提供一个getAuthority()方法,用于获取已认证用户的权限,默认实现类为SimpleGrantedAuthority,源码如下所示:

public final class SimpleGrantedAuthority implements GrantedAuthority {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final String role;

	public SimpleGrantedAuthority(String role) {
		Assert.hasText(role, "A granted authority textual representation is required");
		this.role = role;
	}

	@Override
	public String getAuthority() {
		return this.role;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj instanceof SimpleGrantedAuthority sga) {
			return this.role.equals(sga.getAuthority());
		}
		return false;
	}

	@Override
	public int hashCode() {
		return this.role.hashCode();
	}

	@Override
	public String toString() {
		return this.role;
	}

}

4.5 AuthenticationManager

AuthenticationManager(认证管理器)是一个接口,Spring Security过滤器调用其认证方法进行身份认证,默认实现是ProviderManager

AuthenticationManager源码如下所示:

/**
 * Processes an {@link Authentication} request.
 * 认证管理器 实现认证主要是通过AuthenticationManager接口
 * 在实际开发中,我们可能有多种不同的认证方式,例如:用户名+密码、
 * 邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是AuthenticationManager。
 *
 * @author Ben Alex
 */
public interface AuthenticationManager {

	/**
	 * authenticate()方法主要做三件事:
	 *   如果验证通过,返回Authentication(通常带上authenticated=true)。
	 *   认证失败抛出AuthenticationException
	 *   如果无法确定,则返回null
	 */
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

4.6 ProviderManager

ProviderManager(提供者管理器)默认实现了AuthenticationManager 接口。其源码如下所示:

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {

	private static final Log logger = LogFactory.getLog(ProviderManager.class);

	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();

	private List<AuthenticationProvider> providers = Collections.emptyList();

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private AuthenticationManager parent;

	private boolean eraseCredentialsAfterAuthentication = true;

	/**
	 * Construct a {@link ProviderManager} using the given {@link AuthenticationProvider}s
	 * @param providers the {@link AuthenticationProvider}s to use
	 */
	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	/**
	 * Construct a {@link ProviderManager} using the given {@link AuthenticationProvider}s
	 * @param providers the {@link AuthenticationProvider}s to use
	 */
	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	/**
	 * Construct a {@link ProviderManager} using the provided parameters
	 * @param providers the {@link AuthenticationProvider}s to use
	 * @param parent a parent {@link AuthenticationManager} to fall back to
	 */
	public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}

	@Override
	public void afterPropertiesSet() {
		checkState();
	}

	private void checkState() {
		Assert.isTrue(this.parent != null || !this.providers.isEmpty(),
				"A parent AuthenticationManager or a list of AuthenticationProviders is required");
		Assert.isTrue(!CollectionUtils.contains(this.providers.iterator(), null),
				"providers list cannot contain null values");
	}

	/**
	 * Attempts to authenticate the passed {@link Authentication} object.
	 * <p>
	 * The list of {@link AuthenticationProvider}s will be successively tried until an
	 * <code>AuthenticationProvider</code> indicates it is capable of authenticating the
	 * type of <code>Authentication</code> object passed. Authentication will then be
	 * attempted with that <code>AuthenticationProvider</code>.
	 * <p>
	 * If more than one <code>AuthenticationProvider</code> supports the passed
	 * <code>Authentication</code> object, the first one able to successfully authenticate
	 * the <code>Authentication</code> object determines the <code>result</code>,
	 * overriding any possible <code>AuthenticationException</code> thrown by earlier
	 * supporting <code>AuthenticationProvider</code>s. On successful authentication, no
	 * subsequent <code>AuthenticationProvider</code>s will be tried. If authentication
	 * was not successful by any supporting <code>AuthenticationProvider</code> the last
	 * thrown <code>AuthenticationException</code> will be rethrown.
	 * @param authentication the authentication request object.
	 * @return a fully authenticated object including credentials.
	 * @throws AuthenticationException if authentication fails.
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

	@SuppressWarnings("deprecation")
	private void prepareException(AuthenticationException ex, Authentication auth) {
		this.eventPublisher.publishAuthenticationFailure(ex, auth);
	}

	/**
	 * Copies the authentication details from a source Authentication object to a
	 * destination one, provided the latter does not already have one set.
	 * @param source source authentication
	 * @param dest the destination authentication object
	 */
	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
			AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
			token.setDetails(source.getDetails());
		}
	}

	public List<AuthenticationProvider> getProviders() {
		return this.providers;
	}

	@Override
	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
		this.eventPublisher = eventPublisher;
	}

	/**
	 * If set to, a resulting {@code Authentication} which implements the
	 * {@code CredentialsContainer} interface will have its
	 * {@link CredentialsContainer#eraseCredentials() eraseCredentials} method called
	 * before it is returned from the {@code authenticate()} method.
	 * @param eraseSecretData set to {@literal false} to retain the credentials data in
	 * memory. Defaults to {@literal true}.
	 */
	public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
		this.eraseCredentialsAfterAuthentication = eraseSecretData;
	}

	public boolean isEraseCredentialsAfterAuthentication() {
		return this.eraseCredentialsAfterAuthentication;
	}

	private static final class NullEventPublisher implements AuthenticationEventPublisher {

		@Override
		public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
		}

		@Override
		public void publishAuthenticationSuccess(Authentication authentication) {
		}

	}

}

ProviderManager内部维护了多个AuthenticationProvider (认证提供者),在ProviderManager中的 authenticate(Authentication authentication)方法中,每个AuthenticationProvider实例都有一次机会去校验身份认证是否成功,或者是表明自己不能做出决定并将身份认证交给后面的AuthenticationProvider实例处理。

如果所有的AuthenticationProvider实例中没有一个可以处理当前类型认证的,则会抛出ProviderNotFoundException 异常导致认证失败。ProviderNotFoundException 是一个特殊的AuthenticationException异常类型,用于说明ProviderManager没有配置支持指定类型的认证。

在这里插入图片描述

实际上,每个AuthenticationProvider实例都知道如何执行特定类型的身份验证。例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言
我们只需要实现一个AuthenticationManager,并将我们所需要的 AuthenticationProvider实例配置到里面,这样就能保证我们的程序支持多种验证类型。

ProviderManager还允许配置一个可选的父级AuthenticationManager,当AuthenticationProvider实例无法执行身份验证时,可以咨询该父级AuthenticationManager。父级AuthenticationManager可以是任何类型的AuthenticationManager,但它通常是ProviderManager的一个实例。

在这里插入图片描述

实际上,多个ProviderManager实列可能拥有共同的父级AuthenticationManager。在有多个SecurityFilterChain实例的场景中比较常见,这些SecurityFilterChain实例有一些共同的身份验证(共享的父级AuthenticationManager),但也有不同的身份验证机制(不同的ProviderManager)。

在这里插入图片描述

默认情况下,尝试从成功的身份验证请求返回的对象中清除任何敏感凭据信息。 这样可以防止信息(如密码)在保留时限超过必要的时间。

4.7 AuthenticationProvider

AuthenticationProvider(认证提供者)是一个接口,其中提供两个方式,其源码如下所示:

public interface AuthenticationProvider {

	/**
	 * 
	 * @param 身份验证请求对象。
	 * @return 包括凭证的完全经过身份验证的对象。
	 * 如果AuthenticationProvider无法支持对传递的authentication对象进行身份验证,
	 * 则可能返回null。在这种情况下,将尝试下一个支持所提供的Authentication类的AuthenticationProvider。
	 * AuthenticationException 如果身份验证失败抛出异常
	 */
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

	/**
	 * 
	 * @param authentication。
	 * @return 如果此AuthenticationProvider支持特定类型的Authentication对象,则返回true。
	 * 返回true并不保证AuthenticationProvider能够对呈现的Authentication类实例进行身份验证。它只是表明它可以支持对其进行更密切的评估。
	 * AuthenticationProvider仍然可以从authenticate(Authentication authentication)方法返回null,以表明应该尝试下一个支持所提供的Authentication类的AuthenticationProvider。
	 * 能够执行身份验证的AuthenticationProvider的选择是在ProviderManager运行时进行的。
	 */
	boolean supports(Class<?> authentication);

}

可以在ProviderManager中注入多个AuthenticationProvider。每个AuthenticationProvider执行特定类型的身份验证。

例如:

  • DaoAuthenticationProvider支持基于用户名/密码的身份验证。
  • JwtAuthenticationProvider支持对JWT令牌的身份验证。

Spring Security中默认提供多个ProviderManager实现类,如下图所示:

在这里插入图片描述
可以自定义扩展认证方式,例如:手机验证码等。

4.8 AuthenticationEntryPoint

AuthenticationEntryPoint(认证入口点)用于 ExceptionTranslationFilter发现认证异常时启动身份验证方案,例如:未经身份验证的请求,执行到登录页面的重定向。

AuthenticationEntryPoint有以下实现类:

在这里插入图片描述
例如:LoginUrlAuthenticationEntryPoint,会在表单登录失败或未经身份验证的请求,执行重定向(或转发)到登录表单页面的URL

4.9 AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter是一个抽象类,可以作为用户身份验证的基础,用户名/密码身份验证对应过滤器UsernamePasswordAuthenticationFilter就是继承的AbstractAuthenticationProcessingFilter

例如:自定义手机验证码认证过滤器就可以通过继承AbstractAuthenticationProcessingFilter进行开发,主要是提供认证成功、失败的相关处理:

在这里插入图片描述

①当用户提交凭证之后,AbstractAuthenticationProcessingFilter通过HttpServletRequest创建一个Authentication用于进行身份验证。创建的Authentication的具体类型依赖于AbstractAuthenticationProcessingFilter的子类。

例如UsernamePasswordAuthenticationFilter从在HttpServletRequest中提交的用户名和密码创建一个UsernamePasswordAuthenticationToken

②接着Authentication对象将被传递到AuthenticationManager中进行身份认证。

③如果认证失败:

  • SecurityContextHolder被清空。
  • RememberMeServices.loginFail被调用,如果没有配置remember-me,则不执行。
  • AuthenticationFailureHandler被调用。

④如果认证成功:

  • SessionAuthenticationStrategy收到新登录的通知 。
  • Authentication被设置到SecurityContextHolder,如果需要保存,以便在将来的请求中自动设置它,则必须显式调用SecurityContextRepository#saveContext(详见下文)。
  • RememberMeServices.loginSuccess被调用,如果没有配置remember-me,则不执行。
  • ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent事件。
  • AuthenticationSuccessHandler被调用。

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

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

相关文章

接口测试:Jmeter和Postman测试方法对比

前阶段做了一个小调查&#xff0c;发现软件测试行业做功能测试和接口测试的人相对比较多。在测试工作中&#xff0c;有高手&#xff0c;自然也会有小白&#xff0c;但有一点我们无法否认&#xff0c;就是每一个高手都是从小白开始的&#xff0c;所以今天我们就来谈谈一大部分人…

Leetcode98 验证二叉搜索树

题意理解&#xff1a; 首先明确二叉树的定义&#xff0c;对于所有节点&#xff0c;根节点的值大于左子树所有节点的值&#xff0c;小于右子树所有节点的值。 注意一个误区&#xff1a; 根节点简单和左孩子&#xff0c;右孩子比大小是不够的&#xff0c;要和子树比&#xff0c;…

30.0/集合/ArrayList/LinkedList

目录 30.1什么是集合? 30.1.2为什么使用集合 30.1.3自己创建一个集合类 30.1.3 集合框架有哪些? 30.1.2使用ArrayList集合 30.2增加元素 30.3查询的方法 30.4删除 30.5 修改 30.6泛型 30.1什么是集合? 我们之前讲过数组&#xff0c;数组中它也可以存放多个元素。集合…

C++基础 -6-二维数组,数组指针

二维数组在内存中的存放方式和一维数组完全相同 下表把二维数组抽象成了行列形式方便理解 a[0]指向第一行首元素地址 a指向第一行的首地址 所以a地址和a[0]地址相同,因为起点相同 但a[0]1往右偏移 但a1往下方向偏移 方便理解 an控制行 a[0]n控制列(相当于*an) 数组指针指向二…

【EI会议投稿】第四届物联网与智慧城市国际学术会议(IoTSC 2024)

第四届物联网与智慧城市国际学术会议 2024 4th International Conference on Internet of Things and Smart City 继IoTSC前三届的成功举办&#xff0c;第四届物联网与智慧城市国际学术会议&#xff08;IoTSC 2024&#xff09;将于2024年3月22-24日在河南洛阳举办。 智慧城市的…

Redis常用操作及应用(二)

一、Hash结构 1、常用操作 HSET key field value //存储一个哈希表key的键值 HSETNX key field value //存储一个不存在的哈希表key的键值 HMSET key field value [field value ...] //在一个哈希表key中存储多个键值对 HGET key fie…

二叉树OJ题讲解之一

今天我们一起来做一道初级的二叉树OJ题&#xff0c;都是用递归思想解答 力扣965.单值二叉树 链接https://leetcode.cn/problems/univalued-binary-tree/description/ 所谓单值二叉树就是这棵二叉树的所有节点的值是相同的&#xff0c;那我们要做这道题&#xff0c;肯定要…

sql注入靶场

第一关&#xff1a; 输入&#xff1a;http://127.0.0.1/sqli-labs-master/Less-1/?id1 http://127.0.0.1/sqli-labs-master/Less-1/?id1%27 http://127.0.0.1/sqli-labs-master/Less-1/?id1%27-- 使用--来闭合单引号&#xff0c;证明此处存在字符型的SQL注入。 使用order …

mybatis collection 错误去重

一、需求背景 一条银行产品的需求&#xff0c;不同阶段&#xff0c;可能对应不同的银行(也有可能是同一个银行处理不同的阶段)去处理&#xff0c;如&#xff1a; 发布阶段: —> A银行处理立项阶段: —> B银行处理审核阶段: —> A银行处理出账阶段: —> C银行处理 …

第二十章——多线程

Windows操作系统是多任务操作系统&#xff0c;它以进程为单位。一个进程是一个包含有自身地址的程序&#xff0c;每个独立执行的程序都称为进程。也就是说每个正在执行的程序都是一个进程。系统可以分配给每一个进程有一段有限的使用CPU的时间&#xff08;也可以称为CPU时间片&…

YOLOv8独家原创改进: AKConv(可改变核卷积),即插即用的卷积,效果秒杀DSConv | 2023年11月最新发表

💡💡💡本文全网首发独家改进:可改变核卷积(AKConv),赋予卷积核任意数量的参数和任意采样形状,为网络开销和性能之间的权衡提供更丰富的选择,解决具有固定样本形状和正方形的卷积核不能很好地适应不断变化的目标的问题点,效果秒殺DSConv 1)AKConv替代标准卷积进行…

第二十章总结

继承Thread 类 Thread 类时 java.lang 包中的一个类&#xff0c;从类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立 Thread 实例。 Thread 对象需要一个任务来执行&#xff0c;任务是指线程在启动时执行的工作&#xff0c;start() 方法启动线程&…

什么是交流负载的特点和特性?

交流负载往往存在不平衡的特性&#xff0c;即三相电流和电压的幅值和相位存在差异。这是由于不同负载的性质和使用情况不同导致的&#xff0c;交流负载的功率因数是描述负载对电网的有功功率需求和无功功率需求之间关系的重要参数。功率因数可以分为正功率因数和负功率因数&…

希尔伯特和包络变换

一、希尔伯特变换 Hilbert Transform&#xff0c;数学定义&#xff1a;在数学与信号处理的领域中&#xff0c;一个实值函数的希尔伯特变换是将信号x(t)与h(t)1/(πt)做卷积&#xff0c;以得到其希尔伯特变换。因此&#xff0c;希尔伯特变换结果可以理解为输入是x(t)的线性时不…

仓库代码迁移,从一个仓库迁移到另一个仓库

A-B 1、在B创建一个新的远程仓库 2、 git clone <原Git仓库地址>git remote add origin1 <新Git仓库地址>git push -u origin1 masterorigin1自定义&#xff0c;上下保持一致

机器视觉中精度和分辨率详解!

在机器视觉中&#xff0c;分辨率作为衡量镜头和工业相机的重要参数&#xff0c;被大家熟知。精度是机器视觉中最核心的参数之一。我们一起来了解下这两个参数以及在实际组合应用中&#xff0c;如何有效匹配镜头分辨率和相机分辨率。 精度需要从多个角度来说明&#xff0c;根据…

免费部署开源大模型 ChatGLM-6B

参考&#xff1a;【大模型-第一篇】在阿里云上部署ChatGLM3-CSDN博客 ChatGLM 是一个开源的、支持中英双语的对话语言模型&#xff0c;由智谱 AI 和清华大学 KEG 实验室联合发布&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。ChatGLM3-6B 更…

家政预约服务管理系统,轻松搭建专属家政小程序

家政预约服务管理系统&#xff0c;轻松搭建专属家政小程序app&#xff1b; 家政服务app开发架构包括&#xff1a; 1. 后台管理端&#xff1a;全面管理家政服务、门店、员工、阿姨信息、订单及优惠促销等数据&#xff0c;并进行统计分析。 2. 门店端&#xff1a;助力各门店及员工…

Windows平台下的oracle 11G-11.2.0.4补丁升级操作指南

序号 文件名称 文件说明 1 p6880880_112000_MSWIN-x86-64_OPatch 11.2.0.3.33 for DB 11.2.0.0.0 (Feb 2022) 用于升级 OPatch 2 DB_PSU_11.2.0.4.220118 (Jan 2022)_p33488457_112040_MSWIN-x86-64 主要补丁文件 注意&#xff1a;请用管理员权限运行文件内命令&#…

涂料行业ERP有哪些?涂料行业ERP怎么样

涂料这类产品的生产、包装、存储、运输等方面有特殊的管理要求&#xff0c;有些涂料企业在辅料、原料、半成品、成品、仓库、加工、设备等方面的管理存在不少问题。 如何清晰掌握库存数据&#xff0c;实时了解和掌握经营情况&#xff0c;减少库存过多带来的资金压力和材料浪费…