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

java - SpringBoot3.x接入Security6.x实现JWT认证

java - SpringBoot3.x接入Security6.x实现JWT认证

文章目录

  • java - SpringBoot3.x接入Security6.x实现JWT认证
  • 一、引言
  • 二、环境
  • 三、Maven依赖
  • 四、认识JWT
    • 1. JWT组成
  • 五、认识Security6.x
    • 1. 和旧版本的区别(Security5.7以前的版本)
    • 2. Security6.x的默认筛选器
    • 3. 注册SecurityFilterChain
  • 六、基于OncePerRequestFilter自定义JWT认证筛选器
    • 1. 标记认证成功
  • 七、遇到的问题
    • 1. 加入Security6后,一直出现登录页
    • 2. 配置完匿名访问的URL后,仍然执行自定的筛选器
  • 八、完成JWT认证的主要代码
    • 1. JwtUtil
    • 2. JwtTokenFilter
    • 3. SecuritConfig
  • 总结

一、引言

SpringBoot3.x的安全默认依赖Security6.x,Security6.x于Security5.7以前的配置有了很大区别。我们将深入探讨这两个版本之间的差异,以及它们如何影响现代Web应用的安全架构。特别是,我们将重点分析JWT(JSON Web Tokens)过滤器的工作原理,以及它是如何与匿名访问相结合,为应用提供更加灵活的安全控制。

二、环境

  • JDK 17
  • SpringBoot 3.2
  • Security 6.3

三、Maven依赖

<!-- Security安全 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>3.2.2</version>
</dependency>
<!-- jwt接口认证 -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version>
</dependency>      

四、认识JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

1. JWT组成

JSON Web Token由三部分组成,它们之间用圆点(.)连接,一个典型的JWT看起来是这个样子的:

  • 第一部分:header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等),然后,用Base64对这个JSON编码就得到JWT的第一部分。
  • 第二部分:payload它包含声明(要求),声明是关于实体(通常是用户)和其他数据的声明。
  • 第三部分:签名是用于验证消息在传递过程中有没有被更改,并且对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

注意:不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

{alg: "RS256"
}.
{
//存储自定义的用户信息,属性可以自定扩充login_name: "admin",user_id: "xxxxx",...
}.
[signature]
  • 请求header应该是这样的:Authorization: Bearer

五、认识Security6.x

1. 和旧版本的区别(Security5.7以前的版本)

SpringBoot3中默认Security升级到了6.x写法上发生了很大的变化,最显著的变化之一就是对WebSecurityConfigurerAdapter类的使用方式的改变。这个类在 Spring Security 中被广泛用于自定义安全配置。以下是主要的差异和写法上的变化:

  • 废弃WebSecurityConfigurerAdapter:

在Security5.x 版本中,WebSecurityConfigurerAdapter 是实现安全配置的常用方法。用户通过继承这个类,并覆盖其方法来自定义安全配置。到了 Spring Security 6.x,WebSecurityConfigurerAdapter 被标记为过时(deprecated),意味着它可能在未来的版本中被移除。这一变化是为了推动使用更现代的配置方法,即使用组件式配置。

  • 新版本建议使用组件式配置:

在 Spring Security 6.x 中,推荐使用组件式配置。这意味着你可以创建一个配置类,该类不再需要继承 WebSecurityConfigurerAdapter。
你可以直接定义一个或多个 SecurityFilterChain Bean来配置安全规则。这种方式更加灵活,并且与 Spring Framework 的整体风格更加一致。

2. Security6.x的默认筛选器

支持的所有筛选器在spring-security-config-6.2.1.jar包的org.springframework.security.config.annotation.web.builders.FilterOrderRegistration类的构造函数中定义,并确定了执行顺序。

FilterOrderRegistration() {Step order = new Step(INITIAL_ORDER, ORDER_STEP);put(DisableEncodeUrlFilter.class, order.next());put(ForceEagerSessionCreationFilter.class, order.next());put(ChannelProcessingFilter.class, order.next());order.next(); // gh-8105put(WebAsyncManagerIntegrationFilter.class, order.next());put(SecurityContextHolderFilter.class, order.next());put(SecurityContextPersistenceFilter.class, order.next());put(HeaderWriterFilter.class, order.next());put(CorsFilter.class, order.next());put(CsrfFilter.class, order.next());put(LogoutFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",order.next());this.filterToOrder.put("org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter",order.next());put(X509AuthenticationFilter.class, order.next());put(AbstractPreAuthenticatedProcessingFilter.class, order.next());this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",order.next());this.filterToOrder.put("org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",order.next());put(UsernamePasswordAuthenticationFilter.class, order.next());order.next(); // gh-8105put(DefaultLoginPageGeneratingFilter.class, order.next());put(DefaultLogoutPageGeneratingFilter.class, order.next());put(ConcurrentSessionFilter.class, order.next());put(DigestAuthenticationFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter",order.next());put(BasicAuthenticationFilter.class, order.next());put(RequestCacheAwareFilter.class, order.next());put(SecurityContextHolderAwareRequestFilter.class, order.next());put(JaasApiIntegrationFilter.class, order.next());put(RememberMeAuthenticationFilter.class, order.next());put(AnonymousAuthenticationFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",order.next());put(SessionManagementFilter.class, order.next());put(ExceptionTranslationFilter.class, order.next());put(FilterSecurityInterceptor.class, order.next());put(AuthorizationFilter.class, order.next());put(SwitchUserFilter.class, order.next());
}

3. 注册SecurityFilterChain

    private final String[] permitUrlArr = new String[]{"xxx"};/*** 配置Spring Security安全链。*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//初始化jwt过滤器,并设置jwt公钥var jwtTokenFilter = new JwtTokenFilter();//Security6.x关闭默认登录页httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);logger.info("注册JWT认证SecurityFilterChain");var chain = httpSecurity// 自定义权限拦截规则.authorizeHttpRequests((requests) -> {//requests.anyRequest().permitAll(); //放行所有请求!!!//允许匿名访问requests//自定可匿名访问地址,放到permitAllUrl中即可.requestMatchers(permitUrlArr).permitAll()//除上面声明的可匿名访问地址,其它所有请求全部需要进行认证.anyRequest().authenticated();})// 禁用HTTP响应标头.headers(headersCustomizer -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})//会话设为无状态,基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//添加自定义的JWT认证筛选器,验证header中jwt有效性,将插入到UsernamePasswordAuthenticationFilter之前 .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)//禁用表单登录.formLogin(formLogin -> formLogin.disable())//禁用httpBasic登录.httpBasic(httpBasic -> httpBasic.disable())//禁用rememberMe.rememberMe(rememberMe -> rememberMe.disable())// 禁用CSRF,因为不使用session.csrf(csrf -> csrf.disable())//允许跨域请求.cors(Customizer.withDefaults()).build();return chain;}

六、基于OncePerRequestFilter自定义JWT认证筛选器

使用OncePerRequestFilter的优点是,能保证一个请求只过一次筛选器。可以在filter中实现对jwt的校验,验证成功后需要对Security上下文进行标注。标记认证已经通过,这点非常重要。如果认证完了不标注,后边的过滤器还是认为未认证导致无权限失败。

1. 标记认证成功

//接入Spring Security6.x上下文,标记为已认证状态
JwtAuthenticationToken jwtToken = new JwtAuthenticationToken(null);
jwtToken.setAuthenticated(true); //标记认证通过
SecurityContextHolder.getContext().setAuthentication(jwtToken);

七、遇到的问题

1. 加入Security6后,一直出现登录页

关闭默认登录页有两个设置可以完成,可以删除DefaultLoginPageConfigurer类的加载,或者调用formLogin()函数,具体如下:

    @Beanpublic SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//Security6.x关闭默认登录页httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);var chain = httpSecurity//禁用表单登录.formLogin(formLogin -> formLogin.disable()).build();return chain;}

2. 配置完匿名访问的URL后,仍然执行自定的筛选器

如果出现配置完匿名访问的URL后,仍然执行自定的筛选器,的问题。那原因就在于这个自定义筛选器上了,
只通过requests.requestMatchers(…).permitAll(); 配置的匿名访问只能对默认筛选器起效,如果想
对自定义删除器起效,还需要构建WebSecurityCustomizer Bean对象,基于匿名函数配置要匿名访问的地址。
一下是官网推荐的一个写法,这里建议把两个位置,配置的匿名访问地址,使用一个公共数组进行管理,这样
能保证两个位置配置的一致性。

    /** 其它不需要认证的地址 */private final String[] permitUrlArr = new String[]{"/login","/error"//静态资源,"/static/**.ico","/static/**.js","/static/**.css"//匹配springdoc,"/doc.html","/webjars/**"//匹配swagger路径(默认), "/swagger-ui.html", "/swagger-ui/index.html", "/v3/api-docs/**", "/swagger-ui/**"//监控检测, "/actuator/**"};@Beanpublic WebSecurityCustomizer ignoringCustomize(){return (web) -> web.ignoring().requestMatchers(permitUrlArr);}@Beanpublic SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//初始化jwt过滤器,并设置jwt公钥var jwtTokenFilter = new JwtTokenFilter();//Security6.x关闭默认登录页httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);logger.info("注册JWT认证SecurityFilterChain");var chain = httpSecurity// 自定义权限拦截规则.authorizeHttpRequests((requests) -> {//允许匿名访问requests//自定可匿名访问地址,放到permitAllUrl中即可.requestMatchers(permitUrlArr).permitAll()//除上面声明的可匿名访问地址,其它所有请求全部需要进行认证.anyRequest().authenticated();}).build();return chain;}                    

八、完成JWT认证的主要代码

目前是对已有jwt的认证,下发的jwt是基于RSA加密的内容,需要使用公钥进行解密,公钥一般配置在yml文件里。关键逻辑设计3部分,SecuritConfig、JwtTokenFilter、JwtUtil。

1. JwtUtil

公钥是统一认证中心下发的,目前写在yml中,格式如下:

jwt.keyValue: |-----BEGIN PUBLIC KEY-----xxxxxxxx-----END PUBLIC KEY-----

JwtUtil类提供了验证方法,出于性能考虑使用了单例模式,验证器只需要实例化一次。

public class JwtUtil {private static JwtUtil instance = new JwtUtil();private static JWTVerifier jwtVerifier;//配置文件中公钥的key值private static final String jwtPublicKeyConfig="jwt.keyValue";private JwtUtil()  {}/*** 基于固定配置文件的公钥初始化JWT验证器* @return*/public static JwtUtil getInstance(){if (jwtVerifier == null){String publicKey = SpringUtil.getConfig(jwtPublicKeyConfig);return getInstance(publicKey);}return instance;}/*** 基于自定义公钥初始化JWT验证器* @return*/public static JwtUtil getInstance(String publicKey) {if (jwtVerifier == null){initVerifier(publicKey);}return instance;}// 静态的初始化函数private static synchronized void initVerifier(String publicKey) {if (jwtVerifier != null)return;//替换为实际的Base64编码的RSA公钥字符串String publicKeyStr = publicKey.replaceAll("\\s", "") // 去除所有空白字符,包括换行符.replace("-----BEGINPUBLICKEY-----", "").replace("-----ENDPUBLICKEY-----", "");// 将Base64编码的公钥字符串转换为PublicKey对象byte[] encodedPublicKey = Base64.getDecoder().decode(publicKeyStr);X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedPublicKey);KeyFactory keyFactory = null;try {keyFactory = KeyFactory.getInstance("RSA");PublicKey pubKey = keyFactory.generatePublic(keySpec);// 使用公钥创建一个Algorithm对象,用于验证token的签名Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) pubKey, null);// 解析和验证tokenjwtVerifier = JWT.require(algorithm).build();} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);} catch (InvalidKeySpecException e) {throw new RuntimeException(e);}catch (Exception e){throw new RuntimeException(e);}}/*** 解析和验证JWT token。** @param token JWT token字符串* @return 解码后的JWT对象* @throws Exception 如果解析或验证失败,抛出异常*/public DecodedJWT verifyToken(String token) {return jwtVerifier.verify(token);}
}

2. JwtTokenFilter

该类是校验的主要逻辑,完成了jwt校验、已认证的标注。

public class JwtTokenFilter extends OncePerRequestFilter {private static Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class);private JwtUtil jwtUtil;//获取yml中的配置public String getConfig(String configKey) {var bean = applicationContext.getBean(Environment.class);var val = bean.getProperty(configKey);return val;}public JwtTokenFilter() throws ServletException {String jwtPubliKey = getConfig("jwt.keyValue");initTokenFilter(jwtPubliKey);}public JwtTokenFilter(String jwtPubliKey) throws ServletException {initTokenFilter(jwtPubliKey);}@Overrideprotected void initFilterBean() throws ServletException {}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {var pass = doTokenFilter(request,response,filterChain);if(!pass){return;}filterChain.doFilter(request,response);}/*** 初始化Token过滤器。* @throws ServletException 如果在初始化过程中发生错误,则抛出ServletException异常*/public void  initTokenFilter(String publicKey) throws ServletException {logger.info("初始化TokenFilter");if(StringUtils.isBlank(publicKey)){throw new ServletException("jwtPublicKey is null");}logger.info("jwtPublicKey:{}",publicKey);jwtUtil = JwtUtil.getInstance(publicKey);logger.info("初始化JwtUtil完成");}protected Boolean doTokenFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;// 从请求头中获取tokenString token = request.getHeader("Authorization");if(StringUtils.isBlank(token)){logger.info("jwt token为空,{} {}",request.getMethod(),request.getRequestURI());// 验证失败,返回401状态码response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");return false;}// 假设token是以"Bearer "开头的,需要去掉这个前缀if (token.startsWith("Bearer")) {token = token.replaceAll("Bearer\s+","");}logger.debug(request.getRequestURI());try {// 调用JwtUtils进行token验证DecodedJWT jwtDecode = jwtUtil.verifyToken(token);//接入Spring Security6.x上下文,标记为已认证状态JwtAuthenticationToken jwtToken = new JwtAuthenticationToken(null);jwtToken.setAuthenticated(true);SecurityContextHolder.getContext().setAuthentication(jwtToken);//将登录信息写入spring security上下文} catch (JWTVerificationException ex) {logger.info("jwt token 非法");response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "非法token:"+ex.getMessage());return false;} catch (Exception ex) {throw ex;}logger.debug("token验证通过");return true;}public static class JwtAuthenticationToken extends AbstractAuthenticationToken {private User userInfo;public JwtAuthenticationToken(User user) {super(null);this.userInfo =user;}@Overridepublic User getPrincipal() {return userInfo;}@Overridepublic Object getCredentials() {throw new UnsupportedOperationException();}@Overridepublic boolean implies(Subject subject) {return super.implies(subject);}}}

3. SecuritConfig

该类完成了对需要匿名访问的地址的配置,还有自定义filter的注入。

@Configuration
public class SecurityConfig {private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);/** 其它不需要认证的地址 */private final String[] permitUrlArr = new String[]{"/login","/error"//静态资源,"/static/**.ico","/static/**.js","/static/**.css"//匹配springdoc,"/doc.html","/webjars/**"//匹配swagger路径(默认), "/swagger-ui.html", "/swagger-ui/index.html", "/v3/api-docs/**", "/swagger-ui/**"//监控检测, "/actuator/**"};@Beanpublic WebSecurityCustomizer ignoringCustomize(){return (web) -> web.ignoring().requestMatchers(permitUrlArr);}@Beanpublic SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//初始化jwt过滤器,并设置jwt公钥var jwtTokenFilter = new JwtTokenFilter();//Security6.x关闭默认登录页httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);logger.info("注册JWT认证SecurityFilterChain");var chain = httpSecurity// 自定义权限拦截规则.authorizeHttpRequests((requests) -> {//requests.anyRequest().permitAll(); //放行所有请求!!!//允许匿名访问requests//自定可匿名访问地址,放到permitAllUrl中即可.requestMatchers(permitUrlArr).permitAll()//除上面声明的可匿名访问地址,其它所有请求全部需要进行认证.anyRequest().authenticated();})// 禁用HTTP响应标头.headers(headersCustomizer -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})//会话设为无状态,基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//添加自定义的JWT认证筛选器,验证header中jwt有效性,将插入到UsernamePasswordAuthenticationFilter之前 .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)//禁用表单登录.formLogin(formLogin -> formLogin.disable())//禁用httpBasic登录.httpBasic(httpBasic -> httpBasic.disable())//禁用rememberMe.rememberMe(rememberMe -> rememberMe.disable())// 禁用CSRF,因为不使用session.csrf(csrf -> csrf.disable())//允许跨域请求.cors(Customizer.withDefaults()).build();return chain;}@Beanpublic FilterRegistrationBean disableSpringBootErrorFilter(ErrorPageFilter filter){FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(filter);filterRegistrationBean.setEnabled(false);return filterRegistrationBean;}
}

总结

以上支持介绍了对于已有JWT统一认证系统的接入(JWT解析和认证),不涉及JWT生成和管理相关内容。
目前的用户信息是基于JWT动态解析的,所以暂时没有基于AbstractAuthenticationToken在Security上下文中存放用户信息,JwtAuthenticationToken已经支持自定义用户信息的存储,只需要按需传入即可。基于Security上下文获取用户信息使用SecurityContextHolder.getContext().getAuthentication().getPrincipal();方法。

相关文章:

java - SpringBoot3.x接入Security6.x实现JWT认证

java - SpringBoot3.x接入Security6.x实现JWT认证 文章目录 java - SpringBoot3.x接入Security6.x实现JWT认证一、引言二、环境三、Maven依赖四、认识JWT1. JWT组成 五、认识Security6.x1. 和旧版本的区别&#xff08;Security5.7以前的版本&#xff09;2. Security6.x的默认筛…...

【每日学点鸿蒙知识】无障碍、getLastLocation、蓝牙问题、卡片大小、关系型数据库等

1、是否有类似无障碍辅助相关的API&#xff1f; 场景描述&#xff1a;锁机app&#xff0c;需要通过无障碍能力辅助检测当前正在打开的app&#xff0c;以及模拟用户操作&#xff0c; 关闭用户想要屏蔽的app 可参考&#xff1a;https://developer.huawei.com/consumer/cn/doc/h…...

[Linux] 服务器CPU信息

&#xff08;1&#xff09;查看CPU信息&#xff08;型号&#xff09; cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c输出&#xff1a;可以看到有128个虚拟CPU核心&#xff0c;型号是后面一串 128 Intel(R) Xeon(R) Platinum 8336C CPU 2.30GHz&#xff08;2&…...

MySQL——数据类型

一、常见的数据类型及分类 其中上述的数值类型包含了整形和浮点型&#xff0c;文本、二进制类型主要是字符串类型。 整数类型&#xff08;Integer Types&#xff09;&#xff1a; TINYINT&#xff1a;范围为-128到127或0到255&#xff08;无符号&#xff09;&#xff0c;用于…...

《AI赋能自由职业:开启竞争力提升新征程》

在当今数字化时代&#xff0c;AI技术为自由职业者带来了前所未有的机遇&#xff0c;使其能够在激烈的市场竞争中脱颖而出。以下是自由职业者借助AI提升自身竞争力的几种方法。 利用AI优化工作流程&#xff0c;提高效率 自动化任务处理&#xff1a;自由职业者可以借助自动化工具…...

Excel转Json编辑器工具

功能说明&#xff1a;根据 .xlsx 文件生成对应的 JSON 文件&#xff0c;并自动创建脚本 注意事项 Excel 读取依赖 本功能依赖 EPPlus 库&#xff0c;只能读取 .xlsx 文件。请确保将该脚本放置在 Assets 目录下的 Editor 文件夹中。同时&#xff0c;在 Editor 下再创建一个 Exc…...

创建型设计模式、结构型设计模式与行为型设计模式 上下文任务通用方案 设计模式 大全

设计模式&#xff08;Design Pattern&#xff09;是一种面向对象编程的思想&#xff0c;分为创建型模式、结构型模式与行为型模式三大类&#xff0c;它们提供了在特定上下文中解决常见任务的通用方案&#xff0c;旨在让程序&#xff08;软件&#xff09;具有更好的特点&#xf…...

Mac 环境 VVenC 编译与编码命令行工具使用教程

VVenC VVenC 是一个开源的高效视频编码器&#xff0c;专门用于支持 H.266/VVC (Versatile Video Coding) 标准的编码。H.266/VVC 是继 HEVC (H.265) 之后的新一代视频编码标准&#xff0c;主要目的是提供比 HEVC 更高的压缩效率&#xff0c;同时保持或提高视频质量。H.266/VVC…...

如何在 Ubuntu 22.04 上部署 Nginx 并优化以应对高流量网站教程

简介 本教程将教你如何优化 Nginx&#xff0c;使其能够高效地处理高流量网站。 Nginx 是一个强大且高性能的 Web 服务器&#xff0c;以其高效处理大量并发连接的能力而闻名&#xff0c;这使得它成为高流量网站的流行选择。 正确优化 Nginx 可以显著提高服务器的性能&#xff0…...

springcloud各个组件介绍

Spring Cloud 是一系列框架的集合&#xff0c;它基于 Spring Boot 提供了在分布式系统&#xff08;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话和集群状态&#xff09;中快速构建一些常见模式的工具。下面是对 Sprin…...

HTML5实现好看的喜庆圣诞节网站源码

HTML5实现好看的喜庆圣诞节网站源码 前言一、设计来源1.1 主界面1.2 圣诞介绍界面1.3 圣诞象征界面1.4 圣诞活动界面1.5 圣诞热度界面1.6 圣诞纪念界面1.7 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的喜庆圣诞节网站源码&#xff0c;圣…...

《学习之道》

《学习之道》主要讲述了以下内容&#xff1a; 学习的原理 大脑的两种认知模式&#xff1a;介绍了专注模式和发散模式。专注模式适合集中精力解决具体问题、进行深度理解和记忆推理&#xff0c;但长时间使用易疲惫和陷入思维定式&#xff1b;发散模式则让大脑在更广泛的认知网…...

【Unity3D】ECS入门学习(十一)ComponentSystem、JobComponentSystem

ComponentSystem&#xff1a;仅支持主线程执行&#xff0c;不支持多线程&#xff0c;并且无法使用SystemBase介绍的扩展方法。 using Unity.Entities; using Unity.Transforms; using Unity.Mathematics;/// <summary> /// 仅主线程执行&#xff0c;不支持多线程 /// &l…...

力扣刷题:栈和队列OJ篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 1.用队列实现栈&#xff08;1&#xff09;题目…...

XGPT用户帮助手册

文章目录 2024 更新日志2024.12.272024.12.29 摘要 本文详细介绍了XGPT软件的功能及发展历程。XGPT是一款融合了当前最先进人工智能技术的多模态智能软件&#xff0c;专为国内用户优化设计。除了强大的智能问答功能外&#xff0c;XGPT还结合日常办公和科学研究的需求&#xff0…...

Oracle 数据库 dmp文件从高版本导入低版本的问题处理

当前有个需求是将oracle 19c上的数据备份恢复到oracle 11g上使用。我们通过exp命令远程进行备份&#xff0c;然后通过imp进行恢复时出现IMP-00010: not a valid export file, header failed verification报错。 这是数据库版本问题&#xff0c;在使用exp命令导出的时候使用的客…...

ShardingSphere-Proxy分表场景测试案例

快速入门文章参考&#xff1a;《ShardingSphereProxy:快速入门》 基于K8S部署文章参考&#xff1a;《基于K8s部署ShardingSphere-Proxy》 基于golang的测试用例参考&#xff1a;《ShardingSphere-Proxy 连接实战&#xff1a;从 Golang 原生 SQL 到 GORM 的应用》 背景 我们…...

学技术学英文:Tomcat的线程模型调优

导读&#xff1a; tomcat 线程调优关键需要理解下面这几个参数&#xff1a; 1. maxConnections 描述&#xff1a;指定服务器能够同时接受和处理的最大连接数。也就是说&#xff0c;服务器在任何时候都能处理的最大并发连接数。作用&#xff1a;限制服务器在任何给定时间点能…...

创建flutter项目遇到无法连接源的问题

Flutter 环境信息 Flutter版本: 3.19.4 (channel stable) Framework: revision 68bfaea224 (2024-03-20) Engine: revision a5c24f538d Dart: 3.3.2 DevTools: 2.31.1 项目基本信息 项目路径: D:\F\luichun 域名: www.luichun.com.cn 支持平台: android, web, windows 项目创…...

MAC系统QT图标踩坑记录

MAC系统QT图标踩坑记录 1. 准备图标1.1 方法一&#xff1a;下载准备好的图标1.2 方法二&#xff1a;自己生成图标1.2.1 准备一个png文件1.2.2 用sips生成不同大小的图片1.2.3 用iconutil生成图标文件 2. 配置图标2.1. 把图标改命成自己想要的名字&#xff0c;如icon.icns&#…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Qemu arm操作系统开发环境

使用qemu虚拟arm硬件比较合适。 步骤如下&#xff1a; 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载&#xff0c;下载地址&#xff1a;https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)

错误一&#xff1a;yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因&#xff0c;后面把yaml.safe_dump直接替换成yaml.dump&#xff0c;确实能保存&#xff0c;但出现乱码&#xff1a; 放弃yaml.dump&#xff0c;又切…...

关于 ffmpeg设置摄像头报错“Could not set video options” 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/148515355 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…...