Spring-Security(一)-源码分析及认证流程

news2025/1/15 7:38:58

Spring Security & Oauth2系列:
 
Spring Security(一) 源码分析及认证流程
Spring Security(二)OAuth2认证详解及自定义异常处理

文章目录

  • 1、Spring Security 概述
    • 1.1 Spring Security项目核心模块
  • 1.2 Spring Security 简单集成
      • 1.2.1 使用Security安全功能
  • 2、源码分析-Security安全认证实现
    • 2.1 Security安全认证过程类图
    • 2.2 Security安全认证自动化配置
      • 2.2.1 @EnableWebSecurity
      • 2.2.2 WebSecurityConfiguration
      • 2.2.3 SpringWebMvcImportSelector
      • 2.2.4 OAuth2ImportSelector
      • 2.2.5 HttpSecurityConfiguration
        • 2.2.5.1 HttpSecurity
      • 2.2.6 AuthenticationConfiguration
    • 2.3 验证逻辑
      • 2.3.1 AuthenticationManager
      • 2.3.2 Authentication
      • 2.3.3 ProviderManager
      • 2.3.3 AuthenticationProvider
      • 2.3.4 DaoAuthenticationProvider
      • 2.3.4 AbstractUserDetailsAuthenticationProvider
      • 2.3.4 UserDetailsService
      • 2.3.4.1 UserDetailsService
      • 2.4 认证流程图

1、Spring Security 概述

Spring Security 是能够为Spring 企业应用提供声明式的安全访问控制解决方案的安全框架,为应用系统提供声明式的安全访问控制功能,减少为企业系统安全访问控制编写大量重复的代码。
文章基于Spring Boot 2.3.6 + Oauth 2.0

1.1 Spring Security项目核心模块

spring-security
├── 核心 - spring-security-core.jar
├── Remoting - spring-security-remoting.jar
├── Web - spring-security-web.jar
├── 配置 - spring-security-config.jar
├── LDAP - spring-security-ldap.jar
├── OAuth 2.0核心 - spring-security-oauth2-core.jar
├── OAuth 2.0客户端 - spring-security-oauth2-client.jar
├── OAuth 2.0 JOSE - spring-security-oauth2-jose.jar
├── ACL - spring-security-acl.jar
├── CAS - spring-security-cas.jar
├── OpenID - spring-security-openid.jar
└── 测试 - spring-security-test.jar

核心详细描述参考中文官网

1.2 Spring Security 简单集成

Spring Security 支持Maven和Gradle集成,本文主要使用Spring Boot与Maven:
pom.xml

<dependencies>
    <!-- ... other dependency elements ... -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>

1.2.1 使用Security安全功能

  • 增加配置类,使用注解@EnableWebSecurity开启Web安全功能
  • 实现了接口WebSecurityConfigurer或者继承自WebSecurityConfigurerAdapter),增加Security安全配置。

代码清单:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests((authorize) -> authorize
            .antMatchers("/css/**", "/index").permitAll()
            .antMatchers("/user/**").hasRole("USER")
        )
        .formLogin((formLogin) -> formLogin
            .loginPage("/login")
            .failureUrl("/login-error")
        );
  }
  @Bean
  public UserDetailsService userDetailsService() {
    UserDetails userDetails = User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("USER")
        .build();
    return new InMemoryUserDetailsManager(userDetails);
  }
}

当添加了SecurityConfig 之后我们的应用就具备如下功能:

2、源码分析-Security安全认证实现

2.1 Security安全认证过程类图

类图

2.2 Security安全认证自动化配置

2.2.1 @EnableWebSecurity

从类关系图可以清楚**@EnableWebSecurity**注解是开启Security安全功能的核心注解,EnableWebSecurity源码清单:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;

}

@EnableWebSecurity是组合注解,引入了Impost注解包含的外部配置以及激活了@EnableGlobalAuthentication注解,而@EnableGlobalAuthentication注解激活了AuthenticationConfiguration认证配置类。

2.2.2 WebSecurityConfiguration

WebSecurityConfiguration 是web安全配置核心类,WebSecurityConfiguration最主要的功能就是创建了springSecurityFilterChain Bean,springSecurityFilterChain 是spring security的核心过滤器,是整个认证的入口。WebSecurityConfiguration中完成了声明springSecurityFilterChain的作用,并且最终交给DelegatingFilterProxy这个代理类,负责拦截请求(注意DelegatingFilterProxy这个类不是spring security包中的,而是存在于web包中,spring使用了代理模式来实现安全过滤的解耦)。WebSecurityConfiguration源码清单:

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
//省略==========================================
	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !this.securityFilterChains.isEmpty();
		Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			this.webSecurity.apply(adapter);
		}
		for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
			this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
			customizer.customize(this.webSecurity);
		}
		return this.webSecurity.build();
	}

	//省略=====================

}

2.2.3 SpringWebMvcImportSelector

SpringWebMvcImportSelector主要作用是判断当前的环境是否包含springmvc,因为spring security可以在非spring环境下使用,为了避免DispatcherServlet的重复配置,所以使用了这个注解来区分。

2.2.4 OAuth2ImportSelector

OAuth2ImportSelector类是为了对 OAuth2.0 开放授权协议进行支持。ClientRegistration 如果被引用,具体点也就是 spring-security-oauth2 模块被启用(引入依赖jar)时。会启用 OAuth2 客户端配置 OAuth2ClientConfiguration

2.2.5 HttpSecurityConfiguration

HttpSecurityConfiguration配置类首先通过@Autowired去获取容器中的一个AuthenticationManager实例,如果没能获取到则使用依赖注入的AuthenticationConfiguration实例创建一个AuthenticationManager实例,这个实例其实就是ProviderManager。然后初始化httpSecurity。

2.2.5.1 HttpSecurity

通过HttpSecurity配置指明了Web Security 拦截什么URL、登录认证方式、设置什么权限等。

2.2.6 AuthenticationConfiguration

AuthenticationConfiguration 主要作用就是创建全局的身份认证管理者AuthenticationManager,AuthenticationManager便是最核心的身份认证管理器。
AuthenticationConfiguration源码清单:

@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {

	//省略================

	@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
			ApplicationContext context) {
		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
		AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
				AuthenticationEventPublisher.class);
		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				objectPostProcessor, defaultPasswordEncoder);
		if (authenticationEventPublisher != null) {
			result.authenticationEventPublisher(authenticationEventPublisher);
		}
		return result;
	}

	@Bean
	public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
			ApplicationContext context) {
		return new EnableGlobalAuthenticationAutowiredConfigurer(context);
	}

	@Bean
	public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
			ApplicationContext context) {
		return new InitializeUserDetailsBeanManagerConfigurer(context);
	}

	@Bean
	public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
			ApplicationContext context) {
		return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
	}

	public AuthenticationManager getAuthenticationManager() throws Exception {
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
		AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}
		for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
			authBuilder.apply(config);
		}
		this.authenticationManager = authBuilder.build();
		if (this.authenticationManager == null) {
			this.authenticationManager = getAuthenticationManagerBean();
		}
		this.authenticationManagerInitialized = true;
		return this.authenticationManager;
	}

//省略================

	private AuthenticationManager getAuthenticationManagerBean() {
		return lazyBean(AuthenticationManager.class);
	}

2.3 验证逻辑

2.3.1 AuthenticationManager

AuthenticationManager 提供了认证的入口,源码清单:

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager 接收 Authentication 对象作为参数,并通过 authenticate(Authentication) 方法对其进行验证;AuthenticationProvider实现类用来支撑对 Authentication 对象的验证动作;UsernamePasswordAuthenticationToken实现了 Authentication主要是将用户输入的用户名和密码进行封装,并供给 AuthenticationManager 进行验证;验证完成以后将返回一个认证成功的 Authentication 对象;

2.3.2 Authentication

Authentication 源码:

public interface Authentication extends Principal, Serializable {

	//#1.权限集合
	Collection<? extends GrantedAuthority> getAuthorities();
	//#2. 用户密码认证时,可以理解为密码
    Object getCredentials();
     //#3. 认证时包含的信息
	Object getDetails();
    //# 4. 用户密码认证时,可以理解为用户名
	Object getPrincipal();
    //# 5. 是否被认证,认证为true
	boolean isAuthenticated();
    //# 6. 设置是否能够被认证
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

2.3.3 ProviderManager

ProviderManager 它是 AuthenticationManager 的一个实现类,提供了基本的认证逻辑和方法;它包含了一个 List 对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者(当Spring Security默认提供的实现类不能满足需求的时候可以扩展AuthenticationProvider 覆盖supports(Class<?> authentication) 方法);

@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();
                //遍历所有providers,调用supports验证是否支持当前认证
		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 {
               //调用provider的认证方法进行认证
				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;
	}

2.3.3 AuthenticationProvider

AuthenticationProvider, ProviderManager通过AuthenticationProvider扩展多种认证方法,AuthenticationProvider 本身也就是一个接口,从类图中我们可以看出它的实现类AbstractUserDetailsAuthenticationProvider 和AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider 。

2.3.4 DaoAuthenticationProvider

DaoAuthenticationProvider 是Spring Security中一个核心的Provider,对所有的数据库提供了基本方法和入口。DaoAuthenticationProvider源码清单:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

	/**
	 * 实现用户密码加密,这里我们定义了全局的bean,
	 * 主要是为了保证用户中心和认证中心的密码加密算法一致
	 */
	private PasswordEncoder passwordEncoder;

	//省略

	/**
	 *
	 * 实现 additionalAuthenticationChecks 的验证方法(主要验证密码);
	 */
	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			this.logger.debug("Failed to authenticate since no credentials provided");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("Failed to authenticate since password does not match stored value");
			throw new BadCredentialsException(this.messages
					.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

//省略

	/**
	 * 实现了 AbstractUserDetailsAuthenticationProvider retrieveUser 抽象方法
	 * 主要是通过注入UserDetailsService接口对象,并调用其接口方法 loadUserByUsername(String username)获取得到相关的用户信息。
	 * UserDetailsService接口非常重要。
	 */
	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			//数据库获取用户信息的扩展点
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	//省略
}

2.3.4 AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProvider主要实现了AuthenticationProvider的接口方法 authenticate 并提供了相关的验证逻辑;

    1. 抽象获取用户信息方法
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;
    1. 三步验证工作:
      i. preAuthenticationChecks
      ii. additionalAuthenticationChecks(抽象方法,子类实现)
      iii. postAuthenticationChecks
    1. 将验证结果封装成UsernamePasswordAuthenticationToken返回。

源码清单:

public abstract class AbstractUserDetailsAuthenticationProvider
		implements AuthenticationProvider, InitializingBean, MessageSourceAware {
//省略
	//抽象验证方法(主要验证密码)
	protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;

//省略

	/**
	 *
	 * 实现AuthenticationProvider.authenticate验证方法
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				//1. 获取用户信息
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			//2. 预检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结User接口)
			this.preAuthenticationChecks.check(user);
			//3. 子类实现
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// There was a problem, so try again after checking
			// we're using latest data (i.e. not from the cache)
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		//#4.检测用户密码是否过期对应#2 的User接口
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		//5. 将验证结果封装成UsernamePasswordAuthenticationToken
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

	private String determineUsername(Authentication authentication) {
		return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
	}

	//封装验证结果
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
			UserDetails user) {
		// Ensure we return the original credentials the user supplied,
		// so subsequent attempts are successful even with encoded passwords.
		// Also ensure we return the original getDetails(), so that future
		// authentication events after cache expiry contain the details
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
				authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		this.logger.debug("Authenticated user");
		return result;
	}

	//抽象获取用户信息接口
	protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;

	//省略

}

2.3.4 UserDetailsService

UserDetailsService接口作为桥梁,是DaoAuthenticationProvier与特定用户信息来源进行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所构成;UserDetails和UserDetailsManager各司其责,一个是对基本用户信息进行封装,一个是对基本用户信息进行管理;特别注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用户自定义的扩展点,我们可以继承这些接口提供自己的读取用户来源和管理用户的方法,比如我们可以自己实现一个 与特定 ORM 框架,比如 Mybatis 或者 Hibernate,相关的UserDetailsService和UserDetailsManager;

2.3.4.1 UserDetailsService

UserDetails 验证用户实体

public interface UserDetails extends Serializable {
 #1.权限集合
 Collection<? extends GrantedAuthority> getAuthorities();
 #2.密码	
 String getPassword();
 #3.用户民
 String getUsername();
 #4.用户是否过期
 boolean isAccountNonExpired();
 #5.是否锁定	
 boolean isAccountNonLocked();
 #6.用户密码是否过期	
 boolean isCredentialsNonExpired();
 #7.账号是否可用(可理解为是否删除)
 boolean isEnabled();
}

2.4 认证流程图

认证流程

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

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

相关文章

小猪APP分发:高效的APP托管服务分发平台

有没有遇到过这样的尴尬&#xff1f;辛辛苦苦开发了一个APP&#xff0c;却在托管和分发环节卡壳。想想看&#xff0c;花了那么多时间精力开发的APP&#xff0c;却因为分发不顺利而影响用户体验&#xff0c;实在是让人抓狂。而小猪APP分发就成了你最好的选择。 APP封装分发www.…

LabVIEW如何确保步进电机的长期稳定运行

步进电机因其良好的定位精度和控制性&#xff0c;在自动化设备中得到了广泛应用。然而&#xff0c;长期稳定运行对于任何电机系统都是一个重要的挑战。LabVIEW作为一款强大的图形化编程语言&#xff0c;通过其灵活的控制算法和实时监控能力&#xff0c;为步进电机的稳定运行提供…

探索Adobe XD:高效UI设计软件的中文入门教程

在这个数字化世界里&#xff0c;创意设计不仅是为了吸引观众的注意&#xff0c;也是用户体验的核心部分。强大的设计工具可以帮助设计师创造出明亮的视觉效果&#xff0c;从而提高用户体验。 一、Adobe XD是什么&#xff1f; Adobe XD是一家知名软件公司 Adobe Systems 用户体…

数据加密验签机的工作原理

数据加密验签机&#xff0c;作为网络安全领域的关键设备&#xff0c;其重要性不言而喻。以下是对数据加密验签机的详细介绍&#xff1a; 一、引言 在数字化时代&#xff0c;数据的机密性、完整性和真实性是企业和个人都极为关注的问题。数据加密验签机&#xff0c;正是为了解决…

17.1 命令行-输入与输出、命令行参数、命令行标志

1. 输入与输出 命令行程序被设计为在终端运行。在图形用户界面(GUI)问世以前的年代&#xff0c;命令行程序是与计算机交互的唯一方式。 即便是在图形用户界面大行其道的当今&#xff0c;对程序员和系统管理员来说&#xff0c;命令行程序依然是一种流行而实用的与底层操作系统…

Amazon云计算AWS(二)

目录 三、简单存储服务S3&#xff08;一&#xff09;S3的基本概念和操作&#xff08;二&#xff09;S3的数据一致性模型&#xff08;三&#xff09;S3的安全措施 四、非关系型数据库服务SimpleDB和DynamoDB&#xff08;一&#xff09;非关系型数据库与传统关系数据库的比较&…

【WPF编程宝典】第8讲:形状、画刷和变换

本讲介绍了WPF中的基本形状&#xff0c;包括矩形、椭圆形、直线、折线、多边形、点划线等。还介绍四类画刷的使用。介绍了形状的旋转及移动等变化&#xff0c;元素的旋转及移动等。最后介绍了透明。 1.形状 WPF支持的形状有&#xff1a;Rectangle&#xff0c;Ellipse&#xff…

代码签名证书申请流程

在当今的软件开发与分发过程中&#xff0c;确保代码的安全性和可信度变得至关重要。代码签名证书作为这一安全机制的重要组成部分&#xff0c;不仅帮助开发者保护其软件免遭恶意篡改&#xff0c;也为用户提供了验证软件来源和完整性的手段。 一、什么是代码签名证书 代码签名…

【MySQL】数据库的增删查改

文章目录 前言1. 新增1.1 全插入1.2 指定某些列名插入1.3 多行插入1.4 边查询边插入 2. 约束2.1 非空约束2.2 唯一性约束2.3 默认值约束2.4 主键约束2.5 外键约束2.6 check 约束2.7 外键的逻辑删除 3. 查询 - 初阶3.1 全列查询3.2 指定列查询3.3 指定表达式查询3.4 别名查询3.5…

基于不确定性的相互学习 用于联合医学图像分类和分割

文章目录 Uncertainty-Informed Mutual Learning for Joint Medical Image Classification and Segmentation摘要方法实验结果 Uncertainty-Informed Mutual Learning for Joint Medical Image Classification and Segmentation 摘要 该论文提出了一种基于不确定性的相互学习…

互联网轻量级框架整合之SpringMVC初始化及各组件工作原理

Spring MVC的初始化和流程 MVC理念的发展 SpringMVC是Spring提供给Web应用领域的框架设计&#xff0c;MVC分别是Model-View-Controller的缩写&#xff0c;它是一个设计理念&#xff0c;不仅仅存在于Java中&#xff0c;各类语言及开发均可用&#xff0c;其运转流程和各组件的应…

华为机考入门python3--(33)牛客33-图片整理

分类&#xff1a;排序 知识点&#xff1a; 对字符串中的字符ASCII码排序 sorted(my_str) 题目来自【牛客】 def sort_images(s):# 可以使用ord(A)求A的ASCII值&#xff0c;需要注意的是A的值&#xff08;65&#xff09;比a的值小&#xff08;97&#xff09;sorted_images …

QT4-QT5升级(3)GBK-UTF-8-乱码“常量中有换行符”

乱码有两种&#xff1a;我命名为汉字乱码菱形乱码如下&#xff1a; 1.文件编码为&#xff1a; GB2312 打开编码&#xff1a; GB2312 编译后&#xff1a; QString 部分字符串 常量中有换行符 char * …

【WEEK15】 【DAY3】定时任务【中文版】

2024.6.5 Wednesday 接上文【WEEK15】 【DAY2】【DAY3】邮件任务【中文版】 目录 17.异步、定时、邮件任务17.3.定时任务17.3.1.两个注解&#xff1a;17.3.2.Cron表达式17.3.3.修改Springboot09TestApplication.java开启定时功能的注解17.3.4.新建ScheduledService.java17.3.5.…

操作系统教材第6版——个人笔记5

3.2 单连续分区存储管理 3.2.1 单连续分区存储管理 单连续分区存储管理 每个进程占用一个物理上完全连续的存储空间(区域) 单用户连续分区存储管理固定分区存储管理可变分区存储管理 单用户连续分区存储管理 主存区域划分为系统区与用户区设置一个栅栏寄存器界分两个区域…

搜索与图论:有向图的拓扑序列

搜索与图论&#xff1a;有向图的拓扑序列 题目描述参考代码 题目描述 输入样例 3 3 1 2 2 3 1 3输出样例 1 2 3 参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n, m; int h[N], e…

城镇污水处理设施运维服务认证

初次申请认证时需提交的文件/资料 1、通用文件/资料(证明文件复印件需签字盖公章) ☐ 营业执照复印件、统一社会信用代码/组织机构代码证复印件 ☐ 增值税一般纳税人资格证复印件&#xff0c;或其他增值税一般纳税人资格认定文件复印件 ☐ 资质 或 许可证 复印件&#x…

Mac屏幕截图软件

一、简介&#xff08;有小伙伴留言说想要mac的屏幕截图软件&#xff0c;今天给大家分享一个还不错的&#xff09; 1、一个功能丰富的功能丰富的截图工具&#xff0c;具有许多高级功能&#xff0c;免费。用于快速拍摄并将它们组织成集合。Snappy还支持注释&#xff0c;共享&…

基于Vue的前端瀑布流布局组件的设计与实现

摘要 随着前端技术的不断演进&#xff0c;复杂业务场景和多次迭代后的产品对组件化开发提出了更高的要求。传统的整块应用开发方式已无法满足快速迭代和高效维护的需求。因此&#xff0c;本文将介绍一款基于Vue的瀑布流布局组件&#xff0c;旨在通过组件化开发提升开发效率和降…

免费插件集-illustrator插件-Ai插件-文本对象合并

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;进行文本对象合并。首先从下载网址下载这款插件 https://download.csdn.net/download/m0_67316550/87890501&…