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

微服务OAuth 2.1认证授权可行性方案(Spring Security 6)

文章目录

  • 一、背景
  • 二、微服务架构介绍
  • 三、认证服务器
      • 1. 数据库创建
      • 2. 新建模块
      • 3. 导入依赖和配置
      • 4. 安全认证配置类
  • 四、认证服务器测试
      • 1. AUTHORIZATION_CODE(授权码模式)
          • 1. 获取授权码
          • 2. 获取JWT
      • 2. CLIENT_CREDENTIALS(客户端凭证模式)
  • 五、Gateway
      • 1. 引入依赖
      • 2. 添加白名单文件
      • 3. 全局过滤器
      • 4. 获取远程JWKS
      • 5. 校验JWT
      • 6. 测试(如何携带JWT)
  • 六、后记

一、背景

Oauth2停止维护,基于OAuth 2.1OpenID Connect 1.0Spring Authorization Server模块独立于SpringCloud

本文开发环境如下:

Version
Java17
SpringCloud2023.0.0
SpringBoot3.2.1
Spring Authorization Server1.2.1
Spring Security6.2.1
mysql8.2.0

https://spring.io/projects/spring-security#learn
https://spring.io/projects/spring-authorization-server#learn

二、微服务架构介绍

一个认证服务器(也是一个微服务),专门用于颁发JWT。
一个网关(也是一个微服务),用于白名单判断和JWT校验。
若干微服务。

本文的关键在于以下几点:

  • 搭建认证服务器
  • 网关白名单判断
  • 网关验证JWT
  • 认证服务器如何共享公钥,让其余微服务有JWT自校验的能力。
    在这里插入图片描述

三、认证服务器

这里是官方文档https://spring.io/projects/spring-authorization-server#learn
基本上跟着Getting Started写完就可以。

1. 数据库创建

新建一个数据库xc_users
然后执行jar里自带的三个sql
在这里插入图片描述
这一步官方并没有给出,大概因为可以使用内存存储,在简单demo省去了持久化。不建立数据库可能也是可行的,我没试过。

2. 新建模块

新建一个auth模块,作为认证服务器。

3. 导入依赖和配置

<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId></dependency>
server:servlet:context-path: /authport: 63070
spring:application:name: auth-serviceprofiles:active: devdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.101.65:3306/xc_users?serverTimezone=UTC&userUnicode=true&useSSL=false&username: rootpassword: 1009

4. 安全认证配置类

@Configuration
@EnableWebSecurity
public class AuthServerSecurityConfig {}

里面包含诸多内容,有来自Spring Security的,也有来自的Spring Authorization Server的。

  1. UserDetailsService 的实例,用于检索用户进行身份验证。
    @Beanpublic UserDetailsService userDetailsService() {UserDetails userDetails = User.withUsername("lisi").password("456").roles("read").build();return new InMemoryUserDetailsManager(userDetails);}
  1. 密码编码器(可选,本文不用)
    @Beanpublic PasswordEncoder passwordEncoder() {// 密码为明文方式return NoOpPasswordEncoder.getInstance();// 或使用 BCryptPasswordEncoder
//         return new BCryptPasswordEncoder();}
  1. 协议端点的 Spring Security 过滤器链
@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0http// Redirect to the login page when not authenticated from the// authorization endpoint.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))// Accept access tokens for User Info and/or Client Registration.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}
  1. 用于身份验证的 Spring Security 过滤器链。
    至于哪些要校验身份,哪些不用,根据自己需求写。
@Bean@Order(2)public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/login")).permitAll().requestMatchers(new AntPathRequestMatcher("/oauth2/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.html")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.json")).permitAll().requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll().anyRequest().authenticated()).formLogin(Customizer.withDefaults()).oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));return http.build();}
  1. 自定义验证转化器(可选)
    private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();// 此处可以添加自定义逻辑来提取JWT中的权限等信息// jwtConverter.setJwtGrantedAuthoritiesConverter(...);return jwtConverter;}
  1. 用于管理客户端的 RegisteredClientRepository 实例
	@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("XcWebApp")
//                .clientSecret("{noop}XcWebApp").clientSecret("XcWebApp").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://www.51xuecheng.cn")
//                .postLogoutRedirectUri("http://localhost:63070/login?logout").scope("all").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("message.read").scope("message.write").scope("read").scope("write").clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(2))  // 设置访问令牌的有效期.refreshTokenTimeToLive(Duration.ofDays(3))  // 设置刷新令牌的有效期.reuseRefreshTokens(true)                   // 是否重用刷新令牌.build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}
  1. 用于对访问令牌进行签名的实例
    @Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();}catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}
  1. 用于解码签名访问令牌的JwtDecoder 实例
    @Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}
  1. 用于配置Spring Authorization ServerAuthorizationServerSettings 实例
    @Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}

这里可以设置各种端点的路径,默认路径点开builder()即可看到,如下

public static Builder builder() {return new Builder().authorizationEndpoint("/oauth2/authorize").deviceAuthorizationEndpoint("/oauth2/device_authorization").deviceVerificationEndpoint("/oauth2/device_verification").tokenEndpoint("/oauth2/token").jwkSetEndpoint("/oauth2/jwks").tokenRevocationEndpoint("/oauth2/revoke").tokenIntrospectionEndpoint("/oauth2/introspect").oidcClientRegistrationEndpoint("/connect/register").oidcUserInfoEndpoint("/userinfo").oidcLogoutEndpoint("/connect/logout");}

这里我必须吐槽一下,qnmd /.well-known/jwks.json,浪费我一下午。获取公钥信息的端点现在已经替换成了/oauth2/jwks。

四、认证服务器测试

基本上跟着Getting Started走就行。只不过端点的变动相较于Oauth2很大,还有使用方法上不同。

在配置RegisteredClient的时候,我们设置了三种GrantType,这里只演示两种AUTHORIZATION_CODECLIENT_CREDENTIALS

1. AUTHORIZATION_CODE(授权码模式)

1. 获取授权码

用浏览器打开以下网址,

http://localhost:63070/auth/oauth2/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn

对应oauth2/authorize端点,后面的参数和当时设置RegisteredClient 保持对应就行。response_type一定是code
进入到登陆表单,输入lisi - 456登陆。
在这里插入图片描述
选择all,同意请求。
在这里插入图片描述
url被重定向到http://www.51xuecheng.cn,并携带一个code,这就是授权码。

http://www.51xuecheng.cn/?code=9AexK_KFH1m3GiNBKsc0FU2KkedM2h_6yR-aKF-wPnpQT5USKLTqoZiSkHC3GUvt-56_ky-E3Mv5LbMeH9uyd-S1UV6kfJO6znqAcCAF43Yo4ifxTAQ8opoPJTjLIRUC
2. 获取JWT

使用apifox演示,postmanidea-http都可以。
localhost:63070/auth服务的/oauth2/token端点发送Post请求,同时需要携带认证信息。
认证信息可以如图所填的方法,也可以放到Header中,具体做法是将客户端ID和客户端密码用冒号(:)连接成一个字符串,进行Base64编码放入HTTP请求的Authorization头部中,前缀为Basic 。比如
Authorization: Basic bXlDbGllbnRJZDpteUNsaWVudFNlY3JldA==

在这里插入图片描述
在这里插入图片描述
得到JWT
在这里插入图片描述

2. CLIENT_CREDENTIALS(客户端凭证模式)

不需要授权码,直接向localhost:63070/auth服务的/oauth2/token端点发送Post请求,同时需要携带认证信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、Gateway

至于gateway基础搭建步骤和gateway管理的若干微服务本文不做指导。

相较于auth模块(也就是Authorization Server),gateway的角色是Resource Server
在这里插入图片描述

1. 引入依赖

		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

2. 添加白名单文件

resource下添加security-whitelist.properties文件。
写入以下内容

/auth/**=????
/content/open/**=??????????
/media/open/**=??????????

3. 全局过滤器

在全局过滤器中,加载白名单,然后对请求进行判断。

@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {//白名单private static List<String> whitelist = null;static {//加载白名单try (InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");) {Properties properties = new Properties();properties.load(resourceAsStream);Set<String> strings = properties.stringPropertyNames();whitelist= new ArrayList<>(strings);} catch (Exception e) {log.error("加载/security-whitelist.properties出错:{}",e.getMessage());e.printStackTrace();}}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();log.info("请求={}",requestUrl);AntPathMatcher pathMatcher = new AntPathMatcher();//白名单放行for (String url : whitelist) {if (pathMatcher.match(url, requestUrl)) {return chain.filter(exchange);}}}private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {ServerHttpResponse response = exchange.getResponse();String jsonString = JSON.toJSONString(new RestErrorResponse(error));byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory().wrap(bits);response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");return response.writeWith(Mono.just(buffer));}@Overridepublic int getOrder() {return 0;}
}

4. 获取远程JWKS

yml配置中添加jwk-set-uri属性。

spring:security:oauth2:resourceserver:jwt:jwk-set-uri: http://localhost:63070/auth/oauth2/jwks

新建配置类,自动注入JwtDecoder

@Configuration
public class JwtDecoderConfig {@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")String jwkSetUri;@Beanpublic JwtDecoder jwtDecoderLocal() {return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();}
}

5. 校验JWT

在全局过滤器中补全逻辑。

@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {@Lazy@Autowiredprivate JwtDecoder jwtDecoderLocal;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();log.info("请求={}",requestUrl);AntPathMatcher pathMatcher = new AntPathMatcher();//白名单放行for (String url : whitelist) {if (pathMatcher.match(url, requestUrl)) {return chain.filter(exchange);}}//检查token是否存在String token = getToken(exchange);log.info("token={}",token);if (StringUtils.isBlank(token)) {return buildReturnMono("没有携带Token,没有认证",exchange);}
//        return chain.filter(exchange);try {Jwt jwt = jwtDecoderLocal.decode(token);// 如果没有抛出异常,则表示JWT有效// 此时,您可以根据需要进一步检查JWT的声明log.info("token有效期至:{}", formatInstantTime(jwt.getExpiresAt()));return chain.filter(exchange);} catch (JwtValidationException e) {log.info("token验证失败:{}",e.getMessage());return buildReturnMono("认证token无效",exchange);}}/*** 从请求头Authorization中获取token*/private String getToken(ServerWebExchange exchange) {String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isBlank(tokenStr)) {return null;}String token = tokenStr.split(" ")[1];if (StringUtils.isBlank(token)) {return null;}return token;}/*** 格式化Instant时间** @param expiresAt 在到期* @return {@link String}*/public String formatInstantTime(Instant expiresAt) {// 将Instant转换为系统默认时区的LocalDateTimeLocalDateTime dateTime = LocalDateTime.ofInstant(expiresAt, ZoneId.systemDefault());// 定义日期时间的格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 格式化日期时间并打印return dateTime.format(formatter);}}

6. 测试(如何携带JWT)

携带一个正确的JWTgateway发送请求。
JWT写到HeaderAuthorization字段中,添加前缀Bearer(用空格隔开),向gateway微服务所在地址发送请求。
在这里插入图片描述
gateway日志输出。
在这里插入图片描述

六、后记

颁发JWT都归一个认证服务器管理,校验JWT都归Gateway管理,至于授权,则由各个微服务自己定义。耦合性低、性能较好。

关于授权,可以接着这篇文章。
微服务OAuth 2.1认证授权Demo方案(Spring Security 6)

相关文章:

微服务OAuth 2.1认证授权可行性方案(Spring Security 6)

文章目录 一、背景二、微服务架构介绍三、认证服务器1. 数据库创建2. 新建模块3. 导入依赖和配置4. 安全认证配置类 四、认证服务器测试1. AUTHORIZATION_CODE&#xff08;授权码模式&#xff09;1. 获取授权码2. 获取JWT 2. CLIENT_CREDENTIALS(客户端凭证模式) 五、Gateway1.…...

Maui blazor ios 按设备类型设置是否启用safeArea

需求&#xff0c;新做了个app&#xff0c; 使用的是maui blazor技术&#xff0c;里面用了渐变背景&#xff0c;在默认启用SafeArea情况下&#xff0c;底部背景很突兀 由于现版本maui在SafeArea有点bug&#xff0c;官方教程的<ContentPage SafeAreafalse不生效&#xff0c;于…...

C#系列-使用 Minio 做图片服务器实现图片上传 和下载(13)

1、Minio 服务器下载和安装 要在本地安装和运行 MinIO 服务器&#xff0c;你可以按照以下 步骤进行操作&#xff1a; 1. 访问 MinIO 的官方网站&#xff1a;https://min.io/&#xff0c;然后 点击页面上的”Download”按钮。 2. 在下载页面上&#xff0c;选择适合你操作系统的 …...

生活篇——华为手机去除负一屏

华为手机去除如下图的恶心负一屏 打开华为的应用市场app 进入&#xff1a;我的-设置-国家/地区&#xff08;改为俄罗斯&#xff09;-进入智慧助手检查更新并更新智慧助手。 然后重复开始的操作&#xff0c;将地区改回中国&#xff0c;这样就没有负一屏了。...

2024牛客寒假算法基础集训营2-c Tokitsukaze and Min-Max XOR

来源 题目 Tokitsukaze 有一个长度为 n 的序列 a1,a2,…,an和一个整数 k。 她想知道有多少种序列 b1,b2,…,bm满足&#xff1a; 其中 ⊕\oplus⊕ 为按位异或&#xff0c;具体参见 百度百科&#xff1a;异或 答案可能很大&#xff0c;请输出  mod1e97 后的结果。 输入描述…...

C语言:指针的基础详解

目录 1. 内存 2. 取地址& 3. 指针变量 4. 解引用 4.1 *解引用 4.2 []解引用 4.3 ->解引用 5. 指针变量的大小 5.1 结论 6. 指针运算 7. void* 指针 8. const修饰指针 8.1 const修饰变量 8.2 const修饰指针变量 8.3 结论 9. 野指针 9.1 为什么会出现野指…...

PHP+vue+mysql校园学生社团管理系统574cc

运行环境:phpstudy/wamp/xammp等 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat/phpmyadmin 前台功能&#xff1a; 首页&#xff1a;展示社团信息和活动…...

VS Code中主程序C文件引用了另一个.h头文件,编译时报错找不到函数

目录 一、问题描述二、问题原因三、解决方法四、扩展五、通过CMake进行配置 一、问题描述 VS Code中主程序C文件引用了另一个.h头文件&#xff0c;编译时报错找不到函数 主程序 main.c #include <stdio.h> #include "sumaa.h"int main(int, char**){printf(&q…...

边缘计算:重塑数字世界的未来

引言 随着物联网&#xff08;IoT&#xff09;设备的激增和5G网络的普及&#xff0c;我们正站在一个计算模式的新纪元门槛上——边缘计算。这一技术范式将数据处理和分析推向网络的边缘&#xff0c;即设备或终端&#xff0c;为实时性要求较高的应用提供了前所未有的可能性。 目…...

2024 前端面试题 附录3

这里记录的是昨天和今天原篇的知识点补充 原篇地址&#xff1a; 2024 前端面试题&#xff08;GPT回答 示例代码 解释&#xff09;No.41 - No.60 2024 前端面试题&#xff08;GPT回答 示例代码 解释&#xff09;No.61 - No.100 2024 前端面试题&#xff08;GPT回答 示例代…...

[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.

[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.——> Vue报错&#xff0c;key关键字不唯一&#xff1a; 解决办法&#xff1a;修改一下重复的id值&#xff01;&#xff01;&#xff01;...

Docker-Learn(二)保存、导入、使用Docker镜像

1.保存镜像 根据上一节内容&#xff0c;将创建好镜像进行保存&#xff0c;需要退出当前的已经在运行的docer命令行中断里面&#xff0c;可以通过在终端里面输入指令exit或者按下键盘上的 ctrlD建退出&#xff1a; 回到自己的终端里面&#xff0c;输入指令&#xff1a; docker…...

第三百一十五回

文章目录 1. 概念介绍2. 基本用法3. 补充用法4. 内容总结 我们在上一章回中介绍了"再谈ListView中的分隔线"&#xff0c;本章回中将介绍showMenu的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容…...

区块链(一): 以太坊基础知识

目录 什么是区块链&#xff1f;什么是以太坊&#xff1f;什么是加密货币&#xff1f;以太坊与比特币有什么不同&#xff1f;以太坊能做什么&#xff1f;什么是智能合约&#xff1f;以太坊社区以太坊白皮书 什么是区块链&#xff1f; 区块链是一个交易数据库&#xff0c;在网络…...

高级FPGA开发之基础协议PCIe

基础协议之PCIe部分 一、TLP包的包头 在PCIe的系统中&#xff0c;tlp包的包头的结构有许多部分是相似的&#xff0c;通过掌握这些常规的包头&#xff0c;能帮助理解在PCIe总线上各个设备之间如何进行数据的收发。 通用的字段 通用字段作用Fmt决定了包头是3DW还是3DW&#xff…...

Vue核心基础1:数据代理

1 回顾Object.defineProperty方法 let str hello const person {name: 张三,age: 18 } Object.defineProperty(person, sex, {// value: 男,// enumerable: true, // 控制属性是否可以枚举&#xff0c;默认值是false// writable: true, // 控制属性是否可以被修改&#xff0…...

12 ABC串口接收原理与思路

1. 串口接收原理 基本原理&#xff1a;通过数据起始位判断要是否要开始接收的数据&#xff0c;通过采样的方式确定每一位数据是0还是1。 如何判断数据起始位到来&#xff1a;通过边沿检测电路检测起始信号的下降沿 如何采样&#xff1a;一位数据采多次&#xff0c;统计得到高…...

leetcode(二分查找)34.在排序数组中查找元素的第一个和最后一个位置(C++详细解释)DAY11

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计…...

算法刷题框架

前言&#xff1a;最近积累了一些算法题量&#xff0c;正在刷东神的算法笔记&#xff0c;监督自己记录下读后启发&#xff0c;顺便帮助道友们阅读 数据结构 这一部分老生常谈&#xff0c;数据的存储方式只有顺序存储和链式存储。 最基本的数组和链表对应这两者&#xff0c;栈…...

跟着cherno手搓游戏引擎【24】开启2D引擎前的项目总结(包括前置知识汇总)

前置技术&#xff1a; c动态链接和静态链接&#xff1a; 隐藏的细节&#xff1a;编译与链接_哔哩哔哩_bilibili 【底层】动态链接库(dll)是如何工作的&#xff1f;_哔哩哔哩_bilibili 预编译&#xff0c;编译&#xff0c;汇编&#xff0c;链接 预编译头文件&#xff1a; 为…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...

vue3 daterange正则踩坑

<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...