当前位置: 首页 > news >正文

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

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 {@Overrideprotected 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"));}@Beanpublic 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 {//省略================@Beanpublic 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;}@Beanpublic static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {return new EnableGlobalAuthenticationAutowiredConfigurer(context);}@Beanpublic static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {return new InitializeUserDetailsBeanManagerConfigurer(context);}@Beanpublic 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. 是否被认证,认证为trueboolean isAuthenticated();//# 6. 设置是否能够被认证void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

2.3.3 ProviderManager

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

@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();//遍历所有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 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;}

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接口非常重要。*/@Overrideprotected 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 AbstractUserDetailsAuthenticationProviderimplements AuthenticationProvider, InitializingBean, MessageSourceAware {
//省略//抽象验证方法(主要验证密码)protected abstract void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;//省略/**** 实现AuthenticationProvider.authenticate验证方法*/@Overridepublic 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. 将验证结果封装成UsernamePasswordAuthenticationTokenreturn 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 detailsUsernamePasswordAuthenticationToken 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 认证流程图

认证流程

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 电子元器件批发的几种模式
  • 嵌入式C语言面试题笔试题
  • 华为和锐捷设备流统配置
  • Androidstudio项目加载不出来,显示Connect timed out
  • Android 应用加固与重签名—使用AndroidStudio自带工具 apksigner
  • go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
  • 项目:基于httplib/消息队列负载均衡式在线OJ
  • 【计算机网络】个人学习笔记——第四章 网络层:CIDR编址IP地址子网掩码ICMP路由聚合与子网划分ARP协议
  • 工业无线通信解决方案,企业在进行智能化升级改造
  • vue3模板语法总结
  • ios 获取图片的一部分区域
  • 01 Linux网络设置
  • 调整SinoDB数据库日志模式
  • 十种排序方法
  • 架构设计-用户信息及用户相关的密码信息设计
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • JAVA SE 6 GC调优笔记
  • Laravel核心解读--Facades
  • orm2 中文文档 3.1 模型属性
  • 初探 Vue 生命周期和钩子函数
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 记一次用 NodeJs 实现模拟登录的思路
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 前端临床手札——文件上传
  • 十年未变!安全,谁之责?(下)
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #宝哥教你#查看jquery绑定的事件函数
  • #控制台大学课堂点名问题_课堂随机点名
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $.ajax,axios,fetch三种ajax请求的区别
  • (2)nginx 安装、启停
  • (27)4.8 习题课
  • (el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程
  • (python)数据结构---字典
  • (笔试题)分解质因式
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (附源码)c#+winform实现远程开机(广域网可用)
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)Linux下编译安装log4cxx
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .NET 4.0中的泛型协变和反变
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .net6Api后台+uniapp导出Excel
  • .NET6实现破解Modbus poll点表配置文件
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .pyc文件是什么?
  • @Bean有哪些属性
  • @ModelAttribute注解使用
  • @Pointcut 使用