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.rhkb.cn/news/205297.html

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

相关文章

《golang设计模式》第三部分·行为型模式-06-备忘录模式(Memento)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 备忘录&#xff08;Memento&#xff09;用于在不破坏目标对象封装特性的基础上&#xff0c;将目标对象内部的状态存储到外部对象中&#xff0c;以备之后恢复状态时使用。 1.1 角色 Originato…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(七)

分页查询、删除和修改菜品 1. 菜品分页查询1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码开发1.2.1 设计DTO类1.2.2 设计VO类1.2.3 Controller层1.2.4 Service层接口1.2.5 Service层实现类1.2.6 Mapper层 1.3 功能测试1.3.2 前后端联调测试 2. 删除菜品2.1 需求分析…

可视化大屏时代的到来:智慧城市管理的新思路

随着科技的不断发展&#xff0c;智能芯片作为一种新型的电子元件&#xff0c;被广泛应用于各个领域&#xff0c;其中智慧芯片可视化大屏是一种重要的应用形式。 一、智慧芯片可视化大屏的优势 智慧芯片可视化大屏是一种将智能芯片与大屏幕显示技术相结合的产品&#xff0c;山海…

[pyqt5]pyqt5设置窗口背景图片后上面所有图片都会变成和背景图片一样

pyqt5的控件所有都是集成widget&#xff0c;窗体设置背景图片后控件背景也会跟着改变&#xff0c;此时有2个办法。第一个办法显然我们可以换成其他方式设置窗口背景图片&#xff0c;而不是使用styleSheet样式表&#xff0c;网上有很多其他方法。还有个办法就是仍然用styleSheet…

redis---主从复制及哨兵模式(高可用)

主从复制 主从复制&#xff1a;主从复制是redis实现高可用的基础&#xff0c;哨兵模式和集群都是在主从复制的基础之上实现高可用。 主从负责的工作原理 1、主节点&#xff08;master&#xff09; 从节点&#xff08;slave&#xff09;组成&#xff0c;数据复制是单向的&a…

IP-Adapter:文本兼容图像提示适配器,用于文本到图像扩散模型

IP-Adapter这是一种有效且轻量级的适配器&#xff0c;用于实现预训练文本到图像扩散模型的图像提示功能。只有 22M 参数的 IP 适配器可以实现与微调图像提示模型相当甚至更好的性能。IP-Adapter 不仅可以推广到从同一基本模型微调的其他自定义模型&#xff0c;还可以推广到使用…

Linux小程序之进度条

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;自己能实现进度条 > 毒鸡汤&#xff1a; > …

js双击修改元素内容并提交到后端封装实现

前面发过一个版本了&#xff0c;后来又追加了些功能。重新发一版。新版支持select和radio。 效果图&#xff1a; 右上角带有绿标的&#xff0c;是可以修改的单元格。如果不喜欢显示绿标&#xff0c;可以传递参数时指定不显示&#xff0c;如果想改为其它颜色&#xff0c;也可以…

[Java]JUC并发编程

JUC并发编程 一、什么是JUC 使用到 java.util 工具包、包、分类 二、线程和进程 进程&#xff1a;一个正在运行的程序&#xff0c;QQ.exe Music.exe 程序的集合&#xff1b; 一个进程往往可以包含多个线程&#xff0c;至少包含一个&#xff01; Java默认有两个线程&#x…

浅学指针(3)

系列文章目录 文章目录 系列文章目录前言系列文章目录前言1. 字符指针变量2. 数组指针变量那数组指针变量应该是&#xff1a;存放的应该是数组的地址&#xff0c;能够指向数组的指针变量。2.2 数组指针变量怎么初始化总结&#xff1a;函数名就是地址&#xff0c;&函数名和直…

ubuntu22.04在线安装redis,可选择版本

安装脚本7.0.5版本 在线安装脚本&#xff0c;默认版本号是7.0.5&#xff0c;可以根据需要选择需要的版本进行下载编译安装 sudo apt-get install gcc -y sudo apt-get install pkg-config -y sudo apt-get install build-essential -y#安装redis rm -rf ./tmp.log systemctl …

AI4S Cup学习赛-中枢神经系统药物研发:药物筛选与优化

赛题介绍 链接&#xff1a;Bohrium 案例广场 (dp.tech) 中枢神经系统类疾病长期以来存在着重要的临床未满足需求。据统计&#xff0c;在当前人口老龄化趋势下&#xff0c;阿兹海默&#xff08;AD&#xff09;、帕金森病&#xff08;PD&#xff09;等神经退行性疾病和脑癌、中…

MySQL主从复制架构

MySQL主从复制架构 一、MySQL集群概述 ##1、集群的主要类型 高可用集群&#xff08;High Available Cluster&#xff0c;HA Cluster&#xff09; 高可用集群是指通过特殊的软件把独立的服务器连接起来&#xff0c;组成一个能够提供故障切换&#xff08;Fail Over&#xff09…

【前端系列】前端存档术之keep-alive

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

电子学会C/C++编程等级考试2022年09月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:课程冲突 小 A 修了 n 门课程, 第 i 门课程是从第 ai 天一直上到第 bi 天。 定义两门课程的冲突程度为 : 有几天是这两门课程都要上的。 例如 a1=1,b1=3,a2=2,b2=4 时, 这两门课的冲突程度为 2。 现在你需要求的是这 n 门课…

如何设置Linux终端提示信息

如何设置Linux终端提示信息 1 方法一&#xff1a;只能在VSCode或者Pycharm终端显示提示信息2 方法二&#xff1a;只能在MobaXterm等远程软件上显示提示3 方法三&#xff1a;避免用户没看到上面的提示&#xff0c;上面两种都设置一下 在使用远程终端时&#xff0c;由于多用户使用…

Qt 软件调试(一) Log日志调试

终于这段时间闲下来了&#xff0c;可以系统的编写Qt软件调试的整个系列。前面零零星星的也有部分输出&#xff0c;但终究没有形成体系。借此机会&#xff0c;做一下系统的总结。慎独、精进~ 日志是有效帮助我们快速定位&#xff0c;找到程序异常点的实用方法。但是好的日志才能…

MATLAB | 官方举办的动图绘制大赛 | 第三周赛情回顾

MATHWORKS官方举办的迷你黑客大赛第三期(MATLAB Flipbook Mini Hack)的最新进展&#xff01;&#xff01; 很荣幸前三周都成为了阶段性获奖者~&#xff1a; https://ww2.mathworks.cn/matlabcentral/communitycontests/contests/6/entries/13382 https://ww2.mathworks.cn/mat…

实验一 SAS 基本操作和数据表的导入 2023-11-29

一、上机目的 熟悉SAS的集成环境并掌握它的基本操作。理解SAS程序的结构&#xff0c;理解其中的过程&#xff0c;过程选项&#xff0c;语句&#xff0c;语句选项等概念&#xff0c;掌握SAS编程技术。 二、上机内容 主要有SAS操作界面、SAS窗口操作、SAS菜单操作、SAS按钮操作…

【Java】泛型的简单使用

文章目录 一、包装类1.基本数据类型和对应的包装类2.自动装箱和自动拆箱3.手动装箱和手动拆箱 二、什么是泛型三、泛型的使用四、裸类型&#xff08;Raw Type&#xff09;五、泛型是如何编译的六、泛型的上界七、泛型方法总结 一、包装类 在了解泛型之前我们先了解什么是包装类…