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

一、前言

本章主要学习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;}@Overridepublic 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;}@Overridepublic Authentication getAuthentication() {return this.authentication;}@Overridepublic int hashCode() {return ObjectUtils.nullSafeHashCode(this.authentication);}@Overridepublic void setAuthentication(Authentication authentication) {this.authentication = authentication;}@Overridepublic 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;}@Overridepublic String getAuthority() {return this.role;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj instanceof SimpleGrantedAuthority sga) {return this.role.equals(sga.getAuthority());}return false;}@Overridepublic int hashCode() {return this.role.hashCode();}@Overridepublic 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();}@Overridepublic 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.*/@Overridepublic 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 statusthrow 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 itif (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 itif (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;}@Overridepublic 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 {@Overridepublic void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {}@Overridepublic 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.mzph.cn/news/177447.shtml

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

相关文章

别做无用功!了解伦敦银交易指标的分类

在伦敦银投资中&#xff0c;我们都喜欢使用技术指标来帮助我们判断市场行情、预测银价点位。其实&#xff0c;伦敦银的技术指标是有不同分类的&#xff0c;我们了解主要的几类&#xff0c;这样在交易中才不至于将相同类型的叠加在一起&#xff0c;这样容易降低决策效率。 分类一…

最强学习辅助工具重磅上市:虚拟与现实互动结合助力学习更快一步

太让人震撼了&#xff01;&#xff01;当当狸这款AR智能学习图集打破了传统历史学习材料壁垒 将AR增强现实技术与诗词互动、历史人文、古典建筑巧妙融合 内容真实有趣&#xff0c;全面激发孩子们的学习探索兴趣 妈妈们都想入手的【教学辅助工具】 有了它&#xff0c;孩子学…

2.3基于springboot养老院管理系统

2.3基于springboot养老院管理系统 成品项目已经更新&#xff01;同学们可以打开链接查看&#xff01;需要定做的及时联系我&#xff01;专业团队定做&#xff01;全程包售后&#xff01; 2000套项目视频链接&#xff1a;https://pan.baidu.com/s/1N4L3zMQ9nNm8nvEVfIR2pg?pwd…

docker-compose Install OrangeHRM

OrangeHRM 前言 OrangeHRM 是一个全面的人力资源管理(HRM) 系统,它包含任何企业所需的所有基本功能。OrangeHRM旨在支持任何规模的团队,包括初创企业、中小企业以及大型跨国组织。 OrangeHRM 提前条件 OrangeHRMdocker & docker-composer 安装or

el-table,列表合并,根据名称列名称相同的品名讲其它列值相同的进行合并

el-table,列表合并,根据名称列名称相同的品名讲其它列值相同的进行合并,并且不能垮品名合并 如图 用到el-table合并行的方法合并 tableSpanMethod({ row, column, rowIndex, columnIndex }) {if (column.property "materielName") {//合并商品名const _row this…

Linux后台运行jar包

Linux后台运行jar包 方式一 命令如下&#xff1a; java -jar /data/tools/jar/demo.jar注&#xff1a;/data/tools/jar&#xff1a;指定jar包所在位置&#xff0c;否则要在jar包所在位置运行改命令&#xff1b;当前ssh窗口被锁定&#xff0c;可按CTRL C打断程序运行&#xf…

小功能实现(十八)生成kml文件

引入依赖 <!--解析、生成kml文件类--><dependency><groupId>de.micromata.jak</groupId><artifactId>JavaAPIforKml</artifactId><version>2.2.0</version></dependency>使用方法 注意&#xff1a;需要什么内容可自行添…

CCC联盟数字钥匙(一)——UWB MAC概述

本文在前面已经介绍了相关UWB的PHY之后&#xff0c;重点介绍数字钥匙&#xff08;Digital Key&#xff09;中关于MAC层的相关实现规范。由于MAC层相应涉及内容比较多&#xff0c;本文首先从介绍UWB MAC的整体框架&#xff0c;后续陆续介绍相关的网络、协议等内容。 1、UWB MAC架…

深入redis过程-命令

目录 通用命令 get set keys exists del expire key seconds ttl type 常用数据结构 String类型 SET GET MSET MGET INCR INCRBY INCRBYFLOAT SETNX SETEX Hash类型 HSET key field value HGET key field HMSET HMGET HGETALL HKEYS HVALS HINCRB…

定制开发办公软件在企业发展中的优势|app小程序网站搭建

定制开发办公软件在企业发展中的优势|app小程序网站搭建 如今&#xff0c;办公软件已经成为企业日常工作的必需品。很多企业为了提高工作效率和满足自身业务需要&#xff0c;选择定制开发办公软件。下面将介绍定制开发办公软件在企业发展中的优势。 首先&#xff0c;定制开发办…

Moonbeam生态项目分析 — — 聚合流动性管理协议GAMMA

流动性激励计划Moonbeam Ignite是帮助用户轻松愉快体验Moonbeam生态的趣味活动。在Moonbeam跨链连接的推动下&#xff0c;DeFi的各种可能性在这里爆发。DeFi或许不热门&#xff0c;但总有机会捡漏&#xff0c;了解Monbeam生态项目&#xff0c;我们邀请Moonbeam大使分享他们的研…

智能AIGC写作系统ChatGPT系统源码+Midjourney绘画+支持GPT-4-Turbo模型+支持GPT-4图片对话

一、AI创作系统 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI…

便利高效双赢:无人机油气管道巡检全面升级

我国庞大的油气管道网络&#xff0c;包括原油、成品和天然气管道&#xff0c;因为地理区域广泛、建设年代久远、安全事故频发等现实因素&#xff0c;对管道的安全巡护与管理提出了更高的需求。在这一背景下&#xff0c;传统的人工巡护方式显然已经难以满足对高、精、准的要求。…

shell编程系列- bash和sh的区别

文章目录 引言bash和sh的区别CentOS下的区别Ubuntu下的区别 最佳实践 引言 我们在编写shell脚本时&#xff0c;通常第一行都要声明当前脚本的执行程序&#xff0c;也就是常见的 #!/bin/sh 或者是 #!/bin/bash &#xff0c;我们无论用哪一个脚本似乎都可以正常的执行&#xff0…

【数据结构/C++】栈和队列_循环队列

牺牲一个存储单元来判断队满。 #include<iostream> using namespace std; // 循环队列 #define MaxSize 10 typedef int ElemType; typedef struct {ElemType data[MaxSize];int front, rear; } SqQueue; // 初始化队列 void InitQueue(SqQueue &Q) {// 判断队空 Q.…

MySQL数据库入门到大牛_基础_16_变量、流程控制与游标

文章目录 1. 变量1.1 系统变量1.1.1 系统变量分类1.1.2 查看系统变量 1.2 用户变量1.2.1 用户变量分类1.2.2 会话用户变量1.2.3 局部变量1.2.4 对比会话用户变量与局部变量 2. 定义条件与处理程序2.1 案例分析2.2 定义条件2.3 定义处理程序2.4 案例解决 3. 流程控制3.1 分支结构…

四川劳动保障杂志社四川劳动保障编辑部四川劳动保障杂志2023年第10期目录

主题报道 四川抢抓“金九银十”招聘季多措并举稳就业促就业 举措频“上新” 金秋送岗忙 张玉芳; 2-5 法眼《四川劳动保障》投稿&#xff1a;cnqikantg126.com 筑牢长期护理保险基金安全防线 李科仲;赖晓薇; 6-7 调研 提升职业技能培训工作的举措 寇爵; 8-9 城乡…

实战中使用的策略模式,使用@ConditionalOnProperty实现根据环境注册不同的bean

场景复现 举个例子&#xff0c;针对不同的设备的内存的不同加载一些资源的时候需要采取不同的策略&#xff0c;比如&#xff0c;在内存比较大的设备&#xff0c;可以一次性加载&#xff0c;繁殖需要使用懒加载&#xff0c;这个时候我们就可以采用配置文件配置中心去控制了 Cond…

四川开启智能巡河形式,无人机水利行业应用再创新

在四川省某区域&#xff0c;复亚智能无人机系统以其独特的机场网格化部署得到成功应用&#xff0c;覆盖了该区域内多条市级、省级河流&#xff0c;成为水利行业的新亮点。这一先进系统以无人机水利行业应用为核心&#xff0c;通过网格化和信息化手段&#xff0c;实现了对水域环…

城市内涝积水监测系统,全方位监测城市内涝

在城市规划与防灾减灾的领域中&#xff0c;城市内涝积水监测系统的构建至关重要。随着城市化进程的加快&#xff0c;城市内涝问题日益凸显&#xff0c;对市民的生活质量和生命财产安全构成威胁。为了应对这一问题&#xff0c;建设一套高效、精准的城市内涝积水监测系统势在必行…