详细解析下gRPC examples-RBAC authenication-权限组管理-基于自定义Token

news2024/11/25 2:48:52

详细解析下gRPC examples-RBAC authenication-权限组管理-基于自定义Token

什么是RABC认证?

RBAC (Role-Based Access Control) 授权策略是一种用于控制系统或应用程序中用户或实体对资源的访问权限的方法。在 RBAC 中,访问控制是基于角色的,而不是基于个体用户的。以下是 RBAC 授权策略的核心概念:

  1. 角色(Roles):角色代表了一组用户或实体,这些用户或实体在系统中具有相似的权限需求或角色职责。例如,一个系统可以定义角色如管理员、编辑、普通用户等。

  2. 权限(Permissions):权限是指用户或实体可以执行的操作或访问的资源。每个角色都被赋予一组权限,这些权限决定了该角色能够进行的操作。

  3. 用户分配角色:每个用户或实体被分配到一个或多个角色,这些角色决定了他们在系统中的权限。用户与角色之间的关系可以是多对多的。

简单来说,我们把用户抽象成组别,比方说管理员组,普通用户组,管理员组的用户可以访问后台管理的接口,而普通用户组的用户则没有这个权利

RBAC 授权

此示例使用 google.golang.org/grpc/authz 包中的 StaticInterceptor。它使用基于标头的 RBAC 策略来将每个 gRPC 方法与所需角色匹配。为简单起见,上下文中注入了模拟元数据,其中包括所需的角色,但这应该根据经过身份验证的上下文从适当的服务中获取。

服务端思维导图

Image1

服务端实现流程

首先创立token包,在其中实现对token结构的定义和解密加密方法

// Token 是模拟token用于在grpc客户端发送时带到RPC头部
// 并被服务端用预先制定好的策略进行校验
type Token struct {
	// Secret 被服务端用与校验用户
	Secret string `json:"secret"`
	// Username 被服务端用于在授权的元数据中分配角色。
	Username string `json:"username"`
}

// Encode 返回经过 Base64 编码的JSON令牌  。
// returns a base64 encoded version of the JSON representation of token.
func (t *Token) Encode() (string, error) {
	barr, err := json.Marshal(t)
	if err != nil {
		return "", err
	}
	s := base64.StdEncoding.EncodeToString(barr)
	return s, nil
}

// Decode 使用基于base64来更新Token的状态
// 将令牌以json形式表示
func (t *Token) Decode(s string) error {
	barr, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return err
	}
	return json.Unmarshal(barr, t)
}

接下来要做的是在服务端中定义角色与其对应的权限

const (
	unaryEchoWriterRole      = "UNARY_ECHO:W"
	streamEchoReadWriterRole = "STREAM_ECHO:RW"
	authzPolicy              = `
	{
		"name": "authz",
		"allow_rules": [
			{
				"name": "allow_UnaryEcho",
				"request": {
					"paths": ["/grpc.examples.echo.Echo/UnaryEcho"],
					"headers": [
						{
							"key": "UNARY_ECHO:W",
							"values": ["true"]
						}
					]
				}
			},
			{
				"name": "allow_BidirectionalStreamingEcho",
				"request": {
					"paths": ["/grpc.examples.echo.Echo/BidirectionalStreamingEcho"],
					"headers": [
						{
							"key": "STREAM_ECHO:RW",
							"values": ["true"]
						}
					]
				}
			}
		],
		"deny_rules": []
	}
	`
)

让我来详细解析下这个权限的定义

//- `unaryEchoWriterRole` 表示一种角色,
// 允许客户端进行一元(单向)RPC并拥有写入权限。
//"UNARY_ECHO:W" 是这个角色的名字,表示它可以执行写入操作。
unaryEchoWriterRole = "UNARY_ECHO:W"
// streamEchoReadWriterRole 表示另一种角色,
// 允许客户端进行双向流式(双向通信)RPC并拥有读写权限。
// "STREAM_ECHO:RW" 是这个角色的名字,表示它可以执行读取和写入操作。
 streamEchoReadWriterRole = "STREAM_ECHO:RW"

这些角色可以在服务器端用于授权决策。例如,如果客户端具有 unaryEchoWriterRole 角色,服务器将允许它执行一元RPC的写入操作。如果客户端具有 streamEchoReadWriterRole 角色,服务器将允许它执行双向流式RPC的读取和写入操作。

接下来来解释下authzPolicy这段代码定义了一个名为 authzPolicy 的授权策略,它用于控制哪些操作可以由客户端执行以及哪些角色可以执行这些操作。让我来详细解释:

  • name: 这是授权策略的名称,通常用于标识和引用这个策略。

  • allow_rules: 这是一个允许规则的列表,它指定了哪些操作是允许的。

    • allow_UnaryEcho: 这是一个名为 allow_UnaryEcho 的允许规则,它指定了客户端可以执行一元RPC(单向通信)的操作。

      • request: 这个部分指定了在什么条件下允许执行这个操作。

        • paths: 这里定义了允许的操作路径。在这个例子中,它限定了只有 /grpc.examples.echo.Echo/UnaryEcho 这个路径上的操作可以被执行。

        • headers: 这是一个标头(header)的列表,它定义了在请求中需要满足的标头条件。

          • key: 这个标头的名称是 “UNARY_ECHO:W”,它与前面定义的角色 unaryEchoWriterRole 匹配。

          • values: 在这个情况下,它指定了 “true”,表示只有当客户端拥有 unaryEchoWriterRole 角色时才允许执行这个操作。

    • allow_BidirectionalStreamingEcho: 这是一个名为 allow_BidirectionalStreamingEcho 的允许规则,它指定了客户端可以执行双向流式RPC(双向通信)的操作。它的结构与 allow_UnaryEcho 类似,但是适用于不同的操作路径和角色。

  • deny_rules: 这是一个拒绝规则的列表,用于指定哪些操作是被拒绝的。在这个示例中,没有定义任何拒绝规则,因此所有操作都被默认允许。

  • 总之,authzPolicy 定义了一个授权策略,该策略允许一元RPC和双向流式RPC的执行,但要求客户端具有特定的角色(在标头中指定)才能执行这些操作。

    接下来我们应当在服务端中加载证书并实现静态的拦截器

    	// 创建基于TLS通信的加密端.
    	creds, err := credentials.NewServerTLSFromFile(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
    	if err != nil {
    		log.Fatalf("Loading credentials: %v", err)
    	}
    
    	// 创建一个基于静态策略的验证拦截器
    	staticInteceptor, err := authz.NewStatic(authzPolicy)
    	if err != nil {
    		log.Fatalf("Creating a static authz interceptor: %v", err)
    	}
    

我们通过"google.golang.org/grpc/authz" authz中的NewStatic传入我们先前写好的authzPolicy,它会成功注册基于我们写好的策略的静态拦截器

接下来我们应当实现对于令牌头的校验和验证,让我们看下main函数中

	// grpc.ChainUnaryInterceptor 是 gRPC 框架提供的函数,
	//'它用于创建一组一元拦截器(Unary Interceptors)的链。
	//一元拦截器是 gRPC 中用于拦截一元 RPC 调用的拦截器,这些拦截器可以在请求到达服务器之前或响应返回给客户端之前执行一些额外的逻辑。
	// unaryInts 是一个变量名,用于存储创建的一元拦截器链。
	// authUnaryInterceptor 是一个自定义的一元拦截器函数
	// 它被传递给 grpc.ChainUnaryInterceptor 作为第一个参数。这个拦截器的作用是在每个一元 RPC 调用到达服务器之前,
	// 验证客户端的授权令牌,并为该调用创建一个带有用户名的新上下文。
	// staticInteceptor.UnaryInterceptor 是另一个拦截器,这是从 staticInteceptor 中提取的一元拦截器。
	// staticInteceptor 是一个authz.NewStatic(authzPolicy)创建的授权拦截器,它用于检查是否允许特定的 RPC 调用。这个拦截器会在 authUnaryInterceptor 之后执行。
	unaryInts := grpc.ChainUnaryInterceptor(authUnaryInterceptor, staticInteceptor.UnaryInterceptor)
	streamInts := grpc.ChainStreamInterceptor(authStreamInterceptor, staticInteceptor.StreamInterceptor)

让我们看下authUnaryInterceptor的代码


// authUnaryInterceptor 从传入的RPC context中查找验证头
// 解析username 并创建一个新的context传给解析函数调用
func authUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, errMissingMetadata
	}
	username, err := isAuthenticated(md["authorization"])
	if err != nil {
		return nil, status.Error(codes.Unauthenticated, err.Error())
	}
	//handler 是一个 gRPC 的一元处理函数(UnaryHandler)。它代表了实际的 gRPC 服务端处理逻辑,即在执行拦截器之后要调用的函数,处理客户端的 gRPC 请求。
	//
	//newContextWithRoles(ctx, username) 是一个自定义函数,用于创建一个新的上下文(context)对象,其中包含了用户的角色信息。
	//这个函数在拦截器中的作用是,根据客户端提供的用户名(username)将用户的角色信息添加到上下文中。
	//
	//req 是客户端发送的 gRPC 请求的参数。在这个上下文中,handler 将使用包含角色信息的新上下文来处理请求。
	return handler(newContextWithRoles(ctx, username), req)
}

按照调用的逻辑让我们里一下它做了什么

  • username, err := isAuthenticated(md["authorization"])
    // md["authorization"] 的目的是从 gRPC 请求的元数据中提取授权头部的值。
    
  • 我们首先调用isAuthenticated用于验证授权头部的值是否有效,并解析出其中的用户名。函数返回两个值:usernameerr。如果验证和解析成功,username 将包含用户名,err将为nil;如果发生错误,err将包含一个描述错误的消息,而username` 将为空字符串。

  • 在头部值验证通过的情况下,在执行了拦截器后调用newContextWithRoles,用于创建一个新的上下文(context)对象,其中包含了用户的角色信息。这个函数在拦截器中的作用是,根据客户端提供的用户名(username)将用户的角色信息添加到上下文中。

然后我们注册写好的这些服务

s := grpc.NewServer(grpc.Creds(creds), unaryInts, streamInts)  

// 在这个服务中注册EchoServer  
pb.RegisterEchoServer(s, &server{})

客户端通信

除去复杂的函数实现,我们直接来看main函数的流程和结果

func main() {
	flag.Parse()

	// 创建基于 TLS 的凭证。
	creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
	if err != nil {
		log.Fatalf("加载凭证失败:%v", err)
	}
	// 建立与服务器的连接。
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatalf("grpc.Dial(%q) 失败:%v", *addr, err)
	}
	defer conn.Close()

	// 创建一个回声客户端并发送 RPC 请求。
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client := ecpb.NewEchoClient(conn)

	// 以授权用户的身份进行 RPC 请求,期望它们成功完成。
	authorizedUserTokenCallOption := newCredentialsCallOption(token.Token{Username: "super-user", Secret: "super-secret"})
	if err := callUnaryEcho(ctx, client, "hello world", authorizedUserTokenCallOption); err != nil {
		log.Fatalf("已授权用户的一元 RPC 失败:%v", err)
	}
	if err := callBidiStreamingEcho(ctx, client, authorizedUserTokenCallOption); err != nil {
		log.Fatalf("已授权用户的双向 RPC 失败:%v", err)
	}

	// 以未经授权的用户身份进行 RPC 请求,期望它们失败并返回 PermissionDenied 状态码。
	unauthorizedUserTokenCallOption := newCredentialsCallOption(token.Token{Username: "bad-actor", Secret: "super-secret"})
	if err := callUnaryEcho(ctx, client, "hello world", unauthorizedUserTokenCallOption); err != nil {
		switch c := status.Code(err); c {
		case codes.PermissionDenied:
			log.Printf("未经授权用户的一元 RPC 失败,如预期:%v", err)
		default:
			log.Fatalf("未经授权用户的一元 RPC 失败,但出现意外错误:%v,%v", c, err)
		}
	}
	if err := callBidiStreamingEcho(ctx, client, unauthorizedUserTokenCallOption); err != nil {
		switch c := status.Code(err); c {
		case codes.PermissionDenied:
			log.Printf("未经授权用户的双向 RPC 失败,如预期:%v", err)
		default:
			log.Fatalf("未经授权用户的双向 RPC 失败,但出现意外错误:%v", err)
		}
	}
}

客户端通信结果

UnaryEcho: hello world
BidiStreaming Echo: Request 1
BidiStreaming Echo: Request 2
BidiStreaming Echo: Request 3
BidiStreaming Echo: Request 4
BidiStreaming Echo: Request 5
2023/09/25 19:49:10 未经授权用户的一元 RPC 失败,如预期:rpc error: code = PermissionDenied desc = UnaryEcho RPC failed: rpc error: code = PermissionDenied desc = unauthorized RPC request rejected
2023/09/25 19:49:10 未经授权用户的双向 RPC 失败,如预期:rpc error: code = PermissionDenied desc = receiving StreamingEcho message: rpc error: code = PermissionDenied desc = unauthorized RPC request rejected

思维导图

Image2

项目结构

.
├── client
│   └── main.go
├── README.md
├── server
│   └── main.go
└── token
 └── token.go

试用一下

服务器要求经过身份验证的用户具有以下角色才能授权使用这些方法:

  • UnaryEcho 需要角色 UNARY_ECHO:W
  • BidirectionalStreamingEcho 需要角色 STREAM_ECHO:RW

在接收到请求后,服务器首先检查是否提供了令牌,然后解码令牌并检查是否正确设置了密钥(为简单起见,这里将密钥硬编码为 super-secret,在生产环境中应使用正确的身份验证提供程序)。

如果上述步骤成功,它会使用令牌中的用户名来设置适当的角色(为简单起见,如果用户名与 super-user 匹配,这些角色将被硬编码为上述的 2 个所需角色,但这些角色也应该从外部提供)。

使用以下命令启动服务器:

go run server/main.go

客户端实现演示了如何使用有效的令牌(设置用户名和密钥)与每个端点都将成功返回。它还说明了如何使用错误的令牌将导致服务返回 codes.PermissionDenied

使用以下命令启动客户端:

go run client/main.go

认证

在 gRPC 中,认证被抽象为 credentials.PerRPCCredentials。通常,它还包括授权。用户可以在每个连接或每个调用的基础上进行配置。

目前,认证的示例包括使用 OAuth2 与 gRPC 的示例。

尝试

go run server/main.go

go run client/main.go

OAuth2

OAuth 2.0 协议是目前广泛使用的身份验证和授权机制。gRPC 提供了方便的 API 来配置 OAuth 以与 gRPC 一起使用。请参考 godoc:https://godoc.org/google.golang.org/grpc/credentials/oauth 了解详细信息。

DialOption [WithPerRPCCredentials](https://godoc.org/google.golang.org/grpc#WithPerRPCCredentials)。或者,如果用户希望为每个调用应用 OAuth 令牌,然后使用 grpc RPC 调用配置 CallOption [PerRPCCredentials`](https://godoc.org/google.golang.org/grpc#PerRPCCredentials)。

请注意,OAuth 要求底层传输层是安全的(例如,TLS 等)。

在 gRPC 内部,提供的令牌前缀为令牌类型和一个空格,然后附加到带有键 “authorization” 的元数据中。

客户端实例
/*
 *
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// 该客户端演示了如何为每个RPC提供OAuth2令牌。
package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "time"

    "golang.org/x/oauth2"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/oauth"
    "google.golang.org/grpc/examples/data"
    ecpb "google.golang.org/grpc/examples/features/proto/echo"
)

var addr = flag.String("addr", "localhost:50051", "the address to connect to")

// callUnaryEcho 调用一元 RPC 函数并处理响应。
func callUnaryEcho(client ecpb.EchoClient, message string) {

    // 创建一个带有超时的上下文(Context),以便在超过指定的时间后自动取消操作。
    // context.Background() 创建一个没有任何父上下文的根上下文。
    // 10*time.Second 表示超时时间为10秒。
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

    // 使用 defer 关键字,以确保在函数结束时调用 cancel 函数来取消上下文。
    defer cancel()

    // 使用客户端对象 client 调用一元 RPC 函数 UnaryEcho,并传递上下文 ctx 和请求参数。
    // 在此示例中,我们要发送一条消息给服务器,消息内容为 message 变量的值。
    resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})

    // 检查是否发生了错误。
    if err != nil {
        // 如果发生错误,使用 log.Fatalf 函数记录错误信息并退出程序。
        log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
    }

    // 如果没有发生错误,打印从服务器返回的响应消息。
    fmt.Println("UnaryEcho: ", resp.Message)

}

func main() {
    flag.Parse()

    // 设置连接的凭证。
    perRPC := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(fetchToken())}
    creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
    if err != nil {
        log.Fatalf("failed to load credentials: %v", err)
    }
    opts := []grpc.DialOption{
        // 除了下面的 grpc.DialOption,调用者还可以在 RPC 调用中使用 grpc.CallOption grpc.PerRPCCredentials。
        // 参见:https://godoc.org/google.golang.org/grpc#PerRPCCredentials
        grpc.WithPerRPCCredentials(perRPC),
        // oauth.TokenSource 需要配置传输凭证。
        grpc.WithTransportCredentials(creds),
    }

    conn, err := grpc.Dial(*addr, opts...)
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    rgc := ecpb.NewEchoClient(conn)

    callUnaryEcho(rgc, "hello world")
}

// fetchToken 模拟令牌查找并省略正确令牌获取的详细信息。
// 有关获取OAuth2令牌的示例,请参见:
// https://godoc.org/golang.org/x/oauth2
func fetchToken() *oauth2.Token {
    return &oauth2.Token{
        AccessToken: "some-secret-token",
    }
}

在服务器端,用户通常在拦截器内部获取令牌并进行验证。要获取令牌,请调用 metadata.FromIncomingContext 并传入给定的上下文。它将返回元数据映射。接下来,使用键 “authorization” 获取相应的值,该值是字符串切片。对于 OAuth,切片应只包含一个元素,该元素是格式为 <令牌类型> + " " + <令牌> 的字符串。用户可以通过解析字符串轻松获取令牌,然后验证其有效性。

如果令牌无效,则返回带有错误代码 codes.Unauthenticated 的错误。

如果令牌有效,则调用方法处理程序以开始处理 RPC。

服务端代码实例
/*
 * Copyright 2018 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
//

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

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

相关文章

快速找到离群值的三种方法

本文将介绍3个在数据集中查找离群值的Python方法 离群值&#xff08;Outliers&#xff09;是指在数据集中与其他数据点明显不同或者异常的数据点。这些数据点可能比其他数据点要远离数据集的中心&#xff0c;或者具有异常的数值。离群值可能是由于数据采集错误、异常事件、测量…

大型监控网络设备架构

IT监控架构的功效日益突出&#xff0c;已成为企业信息化建设不可或缺的一部分。本文将详细介绍IT监控架构的含义、构成、功能及其在公司中的应用。 IT监控架构的含义是什么&#xff1f; 简单来说&#xff0c;IT监控架构就是利用一系列技术和方法对公司的IT系统进行全方位的监控…

【LeetCode热题100】--73.矩阵置零

73.矩阵置零 使用标记数组&#xff1a; 使用两个标记数组分别记录每一行和每一列是否有零出现 先遍历一次数组&#xff0c;如果某个元素为0&#xff0c;则将该元素所在的行和列所对应的标记数组的位置为true&#xff0c;最后再遍历该数组&#xff0c;用标记数组更新原数即可 …

《优化接口设计的思路》系列:第四篇—接口的权限控制

系列文章导航 《优化接口设计的思路》系列&#xff1a;第一篇—接口参数的一些弯弯绕绕 《优化接口设计的思路》系列&#xff1a;第二篇—接口用户上下文的设计与实现 《优化接口设计的思路》系列&#xff1a;第三篇—留下用户调用接口的痕迹 《优化接口设计的思路》系列&#…

开学选什么样的电容笔好用?ipad可以用的手写笔

自从ipad等平板电脑开始使用电容笔以来&#xff0c;电容笔已经完全代替了我们的手指&#xff0c;并且使我们的书写速度有了很大的提高。但由于Apple Pencil内置的高科技芯片&#xff0c;价格始终居高不下&#xff0c;这让不少人&#xff0c;尤其是在校学生&#xff0c;也是难以…

DataGrip连接MySQL

DataGrip连接MySQL 新建项目 驱动管理 下载驱动 自定义驱动 如果网络环境不好 无法下载驱动 移除下载方式 指定自定义路径下的驱动 设置连接

linux驱动之input子系统简述

文章目录 一、什么是input子系统二、内核代码三、代码分析 一、什么是input子系统 Input驱动程序是linux输入设备的驱动程序&#xff0c;我们最常见的就按键&#xff0c;触摸&#xff0c;插拔耳机这些。其中事件设备驱动程序是目前通用的驱动程序&#xff0c;可支持键盘、鼠标…

资产连接支持会话分屏,新增Passkey用户认证方式,支持查看在线用户信息,JumpServer堡垒机v3.7.0发布

2023年9月25日&#xff0c;JumpServer开源堡垒机正式发布v3.7.0版本。在这一版本中&#xff0c;在用户管理层面&#xff0c;为了提高使用JumpServer操作资产的效率&#xff0c;JumpServer支持对会话进行分屏操作&#xff0c;用户可以在一个浏览器页面打开多个会话&#xff0c;方…

软件定义网络-OpenvSwitch

软件定义网络&#xff08;SDN&#xff09;。它主要有以下三个特点&#xff1a; 控制与转发分离&#xff1a;转发平面就是一个个虚拟或者物理的网络设备&#xff0c;就像小区里面的一条条路。控制平面就是统一的控制中心&#xff0c;就像小区物业的监控室。它们原来是一起的&…

[python 刷题] 853 Car Fleet

[python 刷题] 853 Car Fleet 哎……周赛第三题解应该用 monotonic stack 做优化的&#xff0c;没写出来&#xff0c;所以多刷两题 monotonic stack 的题目找找感觉…… 题目&#xff1a; There are n cars going to the same destination along a one-lane road. The destin…

【操作系统笔记九】并发安全问题

用户态抢占和内核态抢占 内核中可以执行以下几种程序&#xff1a; ① 当前运行的进程&#xff1a;陷阱程序&#xff08;系统调用&#xff09; 和 故障程序&#xff08;page fault&#xff09; &#xff0c;进程运行在内核态的时候&#xff0c;其实就是在执行进程在用户态触发的…

如何扫描MSI安装文件的路径

今天有个需求&#xff0c;需要扫描已经安装应用, 其中有个华云桌面 其中的UninstallString 值是 MsiExec.exe /X{D20A661B-0CBA-4DE3-A1F6-353D8153725D} 无法直接获取其安装目录&#xff0c; MsiGetProductInfoW 等API INSTALLPROPERTY_INSTALLLOCATION 也不好使 自己写一个…

Supervisor进程管理

Supervisor进程管理 概述&#xff1a;supervisor 是一个用 python 语言编写的进程管理工具&#xff0c;它可以很方便的监听、启动、停止、重启一个或多个进程。当一个进程意外被杀死&#xff0c;supervisor 监听到进程死后&#xff0c;可以很方便的让进程自动恢复&#xff0c;…

区块链实验室(26) - 区块链期刊Blockchain: Research and Applications

Elsevier出版物“Blockchain: Research and Applications”是浙江大学编审的期刊。该期刊自2020年创刊&#xff0c;并出版第1卷。每年出版4期&#xff0c;最新期是第4卷第3期(2023年9月)。 目前没有官方的IF&#xff0c;Elsevier的引用因子Citescore是6.4。 虽然是新刊&#xf…

《开发实战》18 | 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?

取长补短之 Redis vs MySQL 做一个简单测试&#xff0c;分别填充 10 万条数据到 Redis 和 MySQL 中。MySQL 中的 name字段做了索引&#xff0c;相当于 Redis 的 Key&#xff0c;data 字段为 100 字节的数据&#xff0c;相当于 Redis 的Value。在我的电脑上&#xff0c;使用 wr…

免费的视频剪辑素材,可商用。

找免费可商用的视频剪辑素材&#xff0c;就上这6个网站&#xff0c;强推&#xff0c;赶紧收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYxMjky 菜鸟图库网素材非常丰富&#xff0c;网站主要还是以设计类素材为主&#xff0c;高清视频素材也很多&am…

【软件测试】黑盒测试用例的四种设计方法

一、输入域测试用例设计方法 输入域测试法是一种综合考虑了等价类划分、边界值分析等方法的综合方法&#xff0c;针对输入域测试法中可能出现的各种情况&#xff0c;输入域测试法主要考虑三个方面&#xff1a;  (1)极端测试(ExtremalTesting)&#xff0c;要求在输入域中选择…

汽车数字化转型:存储驱动创新未来

通过在存储领域持续不断的技术创新&#xff0c;西部数据正在助力汽车行业打造更加辉煌灿烂的未来。 汽车数字化转型大会上的创新存储 近日&#xff0c;作为智能汽车领域的行业盛宴&#xff0c;2023第二届汽车数字化转型大会在上海揭幕。 本届汽车数字转型大会不但聚集了全球汽车…

Python+selenium自动化生成测试报告

批量执行完用例后&#xff0c;生成的测试报告是文本形式的&#xff0c;不够直观&#xff0c;为了更好的展示测试报告&#xff0c;最好是生成HTML格式的。 unittest里面是不能生成html格式报告的&#xff0c;需要导入一个第三方的模块&#xff1a;HTMLTestRunner 一、导入HTMLT…

springboot如何接入netty,实现在线统计人数?

springboot如何接入netty&#xff0c;实现在线统计人数&#xff1f; Netty 是 一个异步事件驱动的网络应用程序框架 &#xff0c;用于快速开发可维护的高性能协议服务器和客户端。 Netty ​ 是一个 NIO 客户端服务器框架 ​&#xff0c;可以快速轻松地开发协议服务器和客户端等…