微服务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: /auth
  port: 63070
spring:
  application:
    name: auth-service
  profiles:
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.101.65:3306/xc_users?serverTimezone=UTC&userUnicode=true&useSSL=false&
    username: root
    password: 1009

4. 安全认证配置类

@Configuration
@EnableWebSecurity
public class AuthServerSecurityConfig {}

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

  1. UserDetailsService 的实例,用于检索用户进行身份验证。
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User
                .withUsername("lisi")
                .password("456")
                .roles("read")
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }
  1. 密码编码器(可选,本文不用)
    @Bean
    public 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.0
        http

                // 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 实例
	@Bean
    public 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. 用于对访问令牌进行签名的实例
    @Bean
    public 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 实例
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }
  1. 用于配置Spring Authorization ServerAuthorizationServerSettings 实例
    @Bean
    public 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();
        }
    }


    @Override
    public 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));
    }

    @Override
    public 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;
    @Bean
    public JwtDecoder jwtDecoderLocal() {
        return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
    }
}

5. 校验JWT

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

@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {

    @Lazy
    @Autowired
    private JwtDecoder jwtDecoderLocal;

    @Override
    public 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转换为系统默认时区的LocalDateTime
        LocalDateTime 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管理,至于授权,则由各个微服务自己定义。耦合性低、性能较好。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode Python - 1.两数之和

文章目录 题目答案运行结果 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能…

svg基础(五)滤镜-高斯模糊,混合模式,偏移,颜色变换

1 作用 滤镜用于对SVG图形增加特殊效果 2 效果 feBlend - 与图像相结合的滤镜feColorMatrix - 用于彩色滤光片转换feComponentTransferfeCompositefeConvolveMatrixfeDiffuseLightingfeDisplacementMapfeFloodfeGaussianBlur 高斯模糊feImagefeMergefeMorphologyfeOffset - …

VBA中类的解读及应用第九讲:用WithEvents关键字声明实例化对象类变量

《VBA中类的解读及应用》教程【10165646】是我推出的第五套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。 类&#xff0c;是非常抽象的&#xff0c;更具研究的价值。随着我们学习、应用VBA的深入&#xff0…

nacos配置自动刷新源码解析

文章目录 一、前言二、源码解析1、nacos客户端如何监听服务端配置变化的2、ConfigurationProperties注解的bean是如何自动刷新的3、RefreshScope 注解的bean是如何自动刷新的 三、总结 一、前言 最近好奇 nacos 是怎么做到配置自动刷新的&#xff0c;于是就去debug跟了下源码&…

LeetCode 0993. 二叉树的堂兄弟节点:深度优先搜索(BFS)

【LetMeFly】993.二叉树的堂兄弟节点&#xff1a;深度优先搜索(BFS) 力扣题目链接&#xff1a;https://leetcode.cn/problems/cousins-in-binary-tree/ 在二叉树中&#xff0c;根节点位于深度 0 处&#xff0c;每个深度为 k 的节点的子节点位于深度 k1 处。 如果二叉树的两个…

C语言:操作符详解

创作不易&#xff0c;给个三连吧&#xff01;&#xff01; 一、算术操作符 C语言中为了方便计算&#xff0c;提供了算数操作符&#xff0c;分别是:,-,*,/,% 由于这些操作符都是有两个操作数&#xff08;位于操作符两边&#xff09;&#xff0c;所以这种操作符也叫做双目操作…

114.乐理基础-五线谱-快速识别五线谱的谱号

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;113.乐理基础-五线谱-五线谱的调号&#xff08;二&#xff09;-CSDN博客 15个调号&#xff0c;如下图&#xff0c;该怎样才能随便拿出一个来就能快速的知道这是什么调号呢&#xff1f; 一共分为三个要点&#xff1…

【芯片设计- RTL 数字逻辑设计入门 11 -- 移位运算与乘法】

请阅读【嵌入式开发学习必备专栏 】 文章目录 移位运算与乘法Verilog Codeverilog 拼接运算符&#xff08;{}&#xff09;Testbench CodeVCS 波形仿真 问题小结 移位运算与乘法 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输…

Redis篇之分布式锁

一、为什么要使用分布式锁 1.抢劵场景 &#xff08;1&#xff09;代码及流程图 &#xff08;2&#xff09;抢劵执行的正常流程 就是正好线程1执行完整个操作&#xff0c;线程2再执行。 &#xff08;3&#xff09;抢劵执行的非正常流程 因为线程是交替进行的&#xff0c;所以有…

python适配器模式开发实践

1. 什么是适配器设计模式&#xff1f; 适配器&#xff08;Adapter&#xff09;设计模式是一种结构型设计模式&#xff0c;它允许接口不兼容的类之间进行合作。适配器模式充当两个不兼容接口之间的桥梁&#xff0c;使得它们可以一起工作&#xff0c;而无需修改它们的源代码。 …

[linux]:匿名管道和命名管道(什么是管道,怎么创建管道(函数),匿名管道和命名管道的区别,代码例子)

目录 一、匿名管道 1.什么是管道&#xff1f;什么是匿名管道&#xff1f; 2.怎么创建匿名管道&#xff08;函数&#xff09; 3.匿名管道的4种情况 4.匿名管道有5种特性 5.怎么使用匿名管道&#xff1f;匿名管道有什么用&#xff1f;&#xff08;例子&#xff09; 二、命名…

机器人运动学林沛群——旋转矩阵

旋转矩阵 基本概念 三个主轴&#xff0c;可以看作是三个向量&#xff0c;为b在a的表达&#xff0c;以a为基准 旋转矩阵 B相对于A的姿态&#xff1a; B A R [ A X B ^ A Y B ^ A Z B ^ ] [ X ^ B ⋅ X ^ A Y ^ B ⋅ X ^ A Z ^ B ⋅ X ^ A X ^ B ⋅ Y ^ A Y ^ B ⋅ Y ^ A Z …

部署一个自己的P站

效果 安装 1.拉取代码 cd /opt git clone https://gitee.com/WangZhe168_admin/logoly.git 2.安装依赖 cd logoly npm install 3.启动 npm run serve 愉快地使用吧

删除和清空Hive外部表数据

外部表和内部表区别 未被external修饰的是内部表&#xff08;managed table&#xff09;&#xff0c;被external修饰的为外部表&#xff08;external table&#xff09;&#xff1b; 区别&#xff1a; 内部表数据由Hive自身管理&#xff0c;外部表数据由HDFS管理&#xff1b; …

【网站项目】031网络游戏公司官方平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

详解计算机软件基本概念

软件基本概念 软件的定义 一个完整的计算机系统是由硬件系统和软件系统协同工作来完成某一给定的任务的。 只有硬件的计算机称为裸机&#xff0c;裸机必须安装了计算机软件后才可以完成各项任务。 从广义地讲&#xff0c;软件是指计算机程序、数据以及开发、使用和维护程序…

初识 Protobuf 和 gRpc

初步了解 Protobuf 和 gRpc Protocol Buffers Protocol Buffers&#xff08;又称protobuf&#xff09;是谷歌的语言无关、平台无关、可扩展的机制&#xff0c;用于序列化结构化数据。您可以在protobuf的文档中了解更多关于它的信息。 ProtoBuf 的定义 ProtoBuf是将类的定义…

如何在Linux上部署1Panel运维管理面板并实现无公网ip远程访问

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

【大数据】Flink on YARN,如何确定 TaskManager 数

Flink on YARN&#xff0c;如何确定 TaskManager 数 1.问题2.并行度&#xff08;Parallelism&#xff09;3.任务槽&#xff08;Task Slot&#xff09;4.确定 TaskManager 数 1.问题 在 Flink 1.5 Release Notes 中&#xff0c;有这样一段话&#xff0c;直接上截图。 这说明从 …

【lesson48】进程通信之system V(信号量)

文章目录 信号量理解 信号量理解 为了进程通信—>我们需要让不同的进程看到同一份资源---->我们之前讲的所有通信方式&#xff0c;本质都是优先解决一个问题&#xff1a;让不同的进程看到同一份资源。 让不同的进程看到了同一份资源&#xff0c;但是也带来了一些问题&a…