数据库高安全—openGauss安全整体架构安全认证

openGauss作为新一代自治安全数据库,提供了丰富的数据库基础安全能力,并逐步完善各类高阶安全能力。这些安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私保护等。本章节将围绕openGauss安全机制进行源码解读,以帮助数据库内核开发者在进行内核开发时正确的理解和使用安全功能接口,持续为产品提供安全保护能力,或基于当前安全能力进一步开发新的安全能力。

1  openGauss安全整体架构

不同于数据库其他业务模块,安全管理模块并非逻辑集中的。安全管理模块中的安全能力是分散化的,在数据库整个业务逻辑的不同阶段提供对应的安全能力,从而构建数据库整体纵深安全防御能力。一个完整的安全管理整体架构如图1下所示:

图片

图1  openGauss安全机制体系

虽然整个安全机制是分散化的,但是每一个安全子模块都独立负责了一个完整的安全能力。如安全认证机制模块主要解决用户访问控制,登录通道安全问题;用户角色管理模块解决用户创建及用户权限管理问题。因此整体的安全管理体系架构的代码解读也将根据整个体系的划分来进行描述。

认证机制子模块从业务流程上看主要包括认证配置文件管理、用户身份识别、口令校验等过程,其核心流程及接口定义如图2所示。

图片

图2 openGauss安全认证代码接口

用户角色管理子模块从业务流程上看主要包括角色创建、修改、删除、授权和回收,由于openGauss并未严格区分用户和角色,因此用户的管理与角色管理共用一套接口,仅在部分属性上进行区分。角色管理子模块涉及的功能及其对应的接口如图3所示。

图片

图3 openGauss角色管理代码接口

对象访问控制子模块从业务流程看主要包括对象授权、对象权限回收以及实际对象操作时的对象权限检查,其核心流程及接口定义如图4所示。

图片

图4 openGauss对象权限管理代码接口

审计机制子模块主要包括审计日志的创建和管理,以及数据库的各类管理活动和业务活动的审计追溯。审计日志管理包括新创建审计日志、审计日志轮转、审计日志清理。审计日志追溯包括活动发生时的日志记录以及审计信息查询接口,其核心流程及接口定义如图5所示。

图片

图5 openGauss审计线程(左)及审计日志记录(右)接口

2  安全认证

安全认证是数据库对外提供的第一道防线,数据库访问者只有完成身份识别,通过认证校验机制,才可以建立访问通道从事数据库管理活动。在整个安全认证过程中,涉及到用户身份管理识别、用户口令安全存储以及完善的认证机制3大模块,下面的小节将围绕这3个子模块进行涉及原理介绍和代码解析。

2.1 身份标识

安全认证机制要解决的核心问题是谁可以访问数据库的问题。因此在定义身份时,除了描述谁,还要清晰定义整个过程中哪个用户以何种方法访问、从何处访问,访问哪个数据库的问题,因此本小节重点介绍身份识别概念及源码。

身份识别是一个广义的概念,实际上定义了数据库系统的访问规则。openGauss的访问规则信息主要被记录在配置文件HBA(Host-Based Authentication File,主机认证)中,HBA文件中的每一行代表一个访问规则,其书写格式如下:

hostssl   DATABASE USER ADDRESS METHOD [OPTIONS]

其中第1列代表套接字方法,第2列代表允许被访问的数据库,第3列代表允许被访问的用户,第4列代表允许访问的IP地址,第5列代表访问的认证方式,第6列则作为对第五列认证信息的补充。在定义访问规则时,需要按照访问的优先级来组织信息。对于访问需求高的规则建议写在前面。

在openGauss源码中,定义了存储访问规则的关键数据结构HbaLine,核心元素如下所示:

typedef struct HbaLine{    int linenumber;          // 规则行号    ConnType conntype;      // 连接套接字方法    List* databases;        // 允许访问的数据库集合    List* roles;             // 允许访问的用户组    char* hostname;         // 允许访问的IP地址    UserAuth auth_method; // 认证方法} HbaLine;

其中,字段conntype,database,roles,hostname以及auth_method分别对应HBA配置文件中的套接字方法、允许被访问的数据库、允许被访问的用户,IP地址以及当前该规则的认证方法。

当系统管理员配置完HBA文件后,配置文件被存放在数据库服务端侧。当某个用户通过数据库用户发起认证请求时,连接相关的信息都存放在关键数据结构Port中,如下所示:

typedef struct Port {SockAddr laddr;             // 本地进程IP地址信息SockAddr raddr;             // 远端客户端进程IP地址信息char* remote_host;         // 远端host名称字符串或IP地址char* remote_hostname;    // 可选项,远程host名称字符串或IP地址    // 发送给backend的数据包信息,包括访问的数据库名称,用户名,配置参数char* database_name;char* user_name;char* cmdline_options;List* guc_options;    // 认证相关的配置信息HbaLine* hba;    // SSL认证信息#ifdef USE_SSL    SSL* ssl;    X509* peer;    char* peer_cn;    unsigned long count;#endif    // Kerberos认证数据结构信息#ifdef ENABLE_GSS    char* krbsrvname;           // Kerberos服务进程名称    gss_ctx_id_t gss_ctx;         // GSS数据内容    gss_cred_id_t gss_cred;       // 凭证信息    gss_name_t gss_name;            gss_buffer_desc gss_outbuf;    // GSS token信息#endif} Port;

其中Port结构中的user_name、database_name、raddr以及对应的hba等字段就是认证相关的用户信息、访问数据库信息以及IP地址信息。与此同时Port结构中还包含了SSL认证相关的信息以及节点间做Kerberos认证相关的信息。有了Port信息,后台服务线程会根据前端传入的信息与HbaLine中记录的信息逐一比较,完成对应的身份识别。完整的身份标识过程见函数check_hba(),其核心逻辑如下所示:

/**扫描hba文件,寻找匹配连接请求的规则项 */static void check_hba(hbaPort* port){    ……    // 获取当前连接用户的id    roleid = get_role_oid(port->user_name, true);
    foreach (line, t_thrd.libpq_cxt.parsed_hba_lines) {        hba = (HbaLine*)lfirst(line);        // 认证连接行为分为本地连接行为和远程连接行为,需分开考虑        if (hba->conntype == ctLocal) {        // 对于local套接字,仅允许初始安装用户本地登录            if (roleid == INITIAL_USER_ID) {                char sys_user[SYS_USERNAME_MAX + 1];……                // 基于本地环境的uid信息获取当前系统用户名                (void)getpwuid_r(uid, &pwtmp, pwbuf, pwbufsz, &pw);                ……
                // 记录当前系统用户名                securec_check(strncpy_s(sys_user,SYS_USERNAME_MAX+1, pw->pw_name, SYS_USERNAME_MAX), "\0", "\0");
// 对于访问用户与本地系统用户不相匹配的场景,均需提供密码            if (strcmp(port->user_name, sys_user) != 0)                hba->auth_method = uaSHA256;            } else if (hba->auth_method == uaTrust) {                hba->auth_method = uaSHA256;            }……        } else {            // 访问行为为远端访问行为,需要逐条判断包括认证方式在内的信息正确性            if (IS_AF_UNIX(port->raddr.addr.ss_family))                continue;    // SSL连接请求套接字判断#ifdef USE_SSLif (port->ssl != NULL) {                    if (hba->conntype == ctHostNoSSL)                        continue;                } else {                    if (hba->conntype == ctHostSSL)                        continue;                }#else             if (hba->conntype == ctHostSSL)                   continue;#endif               // IP白名单校验               switch (hba->ip_cmp_method) {                   case ipCmpMask:                       if (hba->hostname != NULL) {                           if (!check_hostname(port, hba->hostname))                               continue;                       } else {                           if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr, (struct sockaddr*)&hba->mask))                               continue;                       }                       break;                   case ipCmpAll:                       break;                   case ipCmpSameHost:                   case ipCmpSameNet:                       if (!check_same_host_or_net(&port->raddr, hba->ip_cmp_method))                           continue;                       break;                   default:                       /* shouldn't get here, but deem it no-match if so */                       continue;            }        } /* != ctLocal */
        // 校验数据库信息和用户信息        if (!check_db(port->database_name, port->user_name, roleid, hba->databases))            continue;        if (!check_role(port->user_name, roleid, hba->roles))            continue;        ……        port->hba = hba;        return;    }
    // 没有匹配则拒绝当前连接请求    hba = (HbaLine*)palloc0(sizeof(HbaLine));    hba->auth_method = uaImplicitReject;    port->hba = hba;}

2.2 口令存储

口令是安全认证过程中的重要凭证。openGauss数据库在执行创建用户或修改用户口令操作时,会将口令通过单向哈希方式加密后存储在pg_authid系统表中。口令加密的方式与参数password_encryption_type的配置有关,目前系统支持md5、sha256 + md5(同时存储sha256和md5哈希值)和sha256三种方式,默认采用sha256方式加密。为兼容PostgreSQL社区和第三方工具,openGauss保留了md5方式,此方式安全性较低不推荐用户使用。

口令的加密方式与认证方式密切相关,选择不同的加密方式需要对应的修改pg_hba.conf中的认证方式。口令加密与认证方式对应关系如下表所示:

表1. 口令加密与认证方式

password_encryption_type

加密方式

(hash算法)

认证方式

(pg_hba.conf)

加密函数接口

0

md5

md5

pg_md5_encrypt

1

sha256 + md5

sha256或md5

calculate_encrypted_combined_password

2(默认值)

sha256

sha256

calculate_encrypted_sha256_password

创建用户和修改用户属性的函数入口分别为CreateRole和AlterRole。在函数内对口令加密前,会先校验是否满足口令复杂度,如果满足则调用calculate_encrypted_password函数实现口令的加密。加密时根据参数password_encryption_type配置选择对应的加密方式,加密完成后会清理内存中的敏感信息并返回口令密文。口令加密流程如下图6所示:

图片

图6 口令加密流程图

如图9-6所示,通过调用calculate_encrypted_sha256_password函数实现sha256加密方式,通过调用pg_md5_encrypt函数实现md5方式,而calculate_encrypted_combined_password函数则融合了前面两种加密方式,加密后系统表中包含了sha256和md5两种哈希值。实现sha256加密的calculate_encrypted_sha256_password函数执行流程如图7所示。

图片

图7 calculate_encrypted_sha256_password函数执行流程

2.3 认证机制

整个认证过程中,身份标识完成后,需要完成最后的认证识别。通过用户名和密码来验证数据库用户的身份,判断其是否为合法用户。openGauss使用基于RFC5802协议的口令认证方案,该方案是一套包含服务器和客户端双向认证的用户认证机制。

首先,客户端知道用户名username和密码password,客户端发送用户名username给服务端,服务端检索相应的认证信息,例如:salt、StoredKey、ServerKey和迭代次数。然后,服务端发送盐值salt和迭代次数给客户端。接下来,客户端需要进行一些计算,给服务端发送ClientProof认证信息,服务端通过ClientProof对客户端进行认证,并发送ServerSignature给客户端。最后,客户端通过ServerSignature对服务端进行认证。具体秘钥计算如下所示:

SaltedPassword := Hi(password, salt, iteration_count) 其中,Hi()本质上是PBKDF2。ClientKey := HMAC(SaltedPassword, "Client Key")StoredKey := sha256(ClientKey)ServerKey := HMAC(SaltedPassword, "Sever Key")ClientSignature:=HMAC(StoredKey, token)ServerSignature:= HMAC(ServerKey, token)ClientProof:= ClientSignature XOR ClientKey

具体秘钥衍生过程如图8所示。

图片

图8 秘钥衍生过程

服务器端存的是StoredKey和ServerKey:

StoredKey是用来验证客户端用户身份。

服务端认证客户端通过计算ClientSignature与客户端发来的ClientProof进行异或运算,从而恢复得到ClientKey,然后将其进行HMAC(Hash-based Message Authentication Code,散列信息认证码)运算,将得到的值与StoredKey进行对比,如果相等,证明客户端验证通过。其中ClientSignature通过StoredKey和token(随机数)进行HMAC计算得到。

ServerKey是用来向客户端表明自己身份的。

类似的,客户端认证服务端,通过计算ServerSignature与服务端发来的值进行比较,如果相等,则完成对服务端的认证。其中ServerSignature通过ServerKey和token(随机数)进行HMAC计算得到。

在认证过程中,服务端可以计算出来ClientKey,验证完后直接丢弃不必存储。

防止服务端伪造认证信息ClientProof,从而仿冒客户端。

接下来详细描述在一个认证会话期间的客户端和服务端的信息交换过程。如图9所示:

图片

图9 openGauss认证流程

客户端发送username。

服务端返回盐值salt、iteration-count(迭代次数)、ServerSignature以及随机生成的字符串token给客户端。token是随机生成字符串。服务端通过计算得到的ServerSignature返回给客户端。

ServerSignature := HMAC(ServerKey, token)

客户端认证服务端并发送认证响应。响应信息包含客户端认证信息ClientProof。ClientProof证明客户端拥有ClientKey,但是不通过网络的方式发送。在收到信息后,计算ClientProof。

客户端利用salt和iteration-count,从password计算得到SaltedPassword,然后通过图9中的公式计算得到ClientKey、 StoryKey和ServerKey。

客户端通过StoredKey和token进行哈希计算得到ClientSignature:

ClientSignature := HMAC(StoredKey,token)

通过将ClientKey和ClientSignature进行异或得到ClientProof:

ClientProof := ClientKey XOR ClientSignature

将计算得到的ClientProof和第2步接收的随机字符串发送给服务端进行认证。

服务端接收并校验客户端信息。

使用其保存的StoredKey和token通过HMAC算法进行计算,然后与客户端传来的ClientProof进行异或,恢复ClientKey,再对ClientKey进行哈希计算,得到的结果与服务端保存的StoredKey进行比较。如果相等,则服务端对客户端的认证通过。否则,认证失败。

ClientSignature := HMAC(StoredKey, token)
HMAC(ClientProof XOR ClientSignature ) = StoredKey

客户端认证的过程通过调用ClientAuthentication函数完成,该函数只有一个类型Port的参数,Port结构中存储着客户端相关信息。完整的客户端认证过程见函数ClientAuthentication (),如下所示:

void ClientAuthentication(Port* port){    int status = STATUS_ERROR;    char details[PGAUDIT_MAXLENGTH] = {0};    char token[TOKEN_LENGTH + 1] = {0};    errno_t rc = EOK;    GS_UINT32 retval = 0;hba_getauthmethod(port);……    switch (port->hba->auth_method) {        case uaReject:……case uaImplicitReject:        ……// 使用MD5口令认证case uaMD5:            sendAuthRequest(port, AUTH_REQ_MD5);            status = recv_and_check_password_packet(port);            break;// 使用sha256认证方法case uaSHA256:            // 禁止使用初始用户进行远程连接            if (isRemoteInitialUser(port)) {                ereport(FATAL,                  (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("Forbid remote connection with initial user.")));    }    rc = memset_s(port->token, TOKEN_LENGTH * 2 + 1, 0, TOKEN_LENGTH * 2 + 1);    securec_check(rc, "\0", "\0");    HOLD_INTERRUPTS();    // 生成随机数token    retval = RAND_priv_bytes ((GS_UCHAR*)token, (GS_UINT32)TOKEN_LENGTH);    RESUME_INTERRUPTS();    CHECK_FOR_INTERRUPTS();    if (retval != 1) {        ereport(ERROR, (errmsg("Failed to Generate the random number,errcode:%u", retval)));    }    sha_bytes_to_hex8((uint8*)token, port->token);    port->token[TOKEN_LENGTH * 2] = '\0';    // 发送认证请求到前端,认证码为AUTH_REQ_SHA256    sendAuthRequest(port, AUTH_REQ_SHA256);    // 接收并校验客户端的信息    status = recv_and_check_password_packet(port);    break;……}……if (status == STATUS_OK)    sendAuthRequest(port, AUTH_REQ_OK);else {    auth_failed(port, status);}
// 完成认证,关闭参数ImmediateInterruptOKt_thrd.int_cxt.ImmediateInterruptOK = false;}

在这个ClientAuthentication函数中,通过调用函数hba_getauthmethod,然后调用check_hba函数,检查客户端地址、所连接数据库、用户名在文件HBA中是否有能匹配的HBA记录。如果能够找到匹配的HBA记录,则将Port结构中相关认证方法的字段设置为HBA记录中的参数,同时状态值为STATUS_OK。然后,根据不同的认证方法,进行相应的认证过程。具体认证方法如表2。在认证过程中可能需要和客户端进行多次交互。最后返回如果为STAUS_OK,则表示认证成功,并将认证成功的信息发送回客户端,否则发送认证失败的信息。

表2 认证方法

认证方法

描述

uaReject

0

无条件的拒绝连接

uaTrust

3

无条件的允许连接,这个方法允许被该HBA记录匹配的客户端直接连入数据库。

uaMD5

5

要求客户端提供一个MD5加密口令进行认证。

uaSHA256

6

要求客户端提供SHA256加密口令进行认证。

uaGSS

7

通过GSSAPI认证用户。

接下来介绍客户端认证服务端并发送认证响应。客户端根据不同的认证方法进行不同的处理过程,当前方法为AUTH_REQ_SHA256时,通过调用函数pg_password_sendauth完成对服务端的认证,如下所示:

static int pg_password_sendauth(PGconn* conn, const char* password, AuthRequest areq){int ret;// 初始化变量……    char h[HMAC_LENGTH + 1] = {0};    char h_string[HMAC_LENGTH * 2 + 1] = {0};    char hmac_result[HMAC_LENGTH + 1] = {0};    char client_key_bytes[HMAC_LENGTH + 1] = {0};    switch (areq) {      case AUTH_REQ_MD5: // pg_md5_encrypt()通过MD5Salt进行MD5加密。……  case AUTH_REQ_MD5_SHA256:……      case AUTH_REQ_SHA256: {        char* crypt_pwd2 = NULL;        if (SHA256_PASSWORD == conn->password_stored_method || PLAIN_PASSWORD == conn->password_stored_method) {            // 通过sha256方式加密密码            if (!pg_sha256_encrypt(                    password, conn->salt, strlen(conn->salt), (char*)buf, client_key_buf, conn->iteration_count))                return STATUS_ERROR;
            rc = strncpy_s(server_key_string,                sizeof(server_key_string),                &buf[SHA256_LENGTH + SALT_STRING_LENGTH],                sizeof(server_key_string) - 1);            securec_check_c(rc, "\0", "\0");            rc = strncpy_s(stored_key_string,                sizeof(stored_key_string),                &buf[SHA256_LENGTH + SALT_STRING_LENGTH + HMAC_STRING_LENGTH],                sizeof(stored_key_string) - 1);            securec_check_c(rc, "\0", "\0");            server_key_string[sizeof(server_key_string) - 1] = '\0';            stored_key_string[sizeof(stored_key_string) - 1] = '\0';
            sha_hex_to_bytes32(server_key_bytes, server_key_string);            sha_hex_to_bytes4(token, conn->token);// 通过server_key和token调用HMAC算法计算,得到client_server_signature_bytes,通过该变量转为字符串变量,用来验证与服务端传来的server_signature是否相等。            CRYPT_hmac_ret1 = CRYPT_hmac(NID_hmacWithSHA256,                (GS_UCHAR*)server_key_bytes,                HMAC_LENGTH,                (GS_UCHAR*)token,                TOKEN_LENGTH,                (GS_UCHAR*)client_server_signature_bytes,                (GS_UINT32*)&hmac_length);            if (CRYPT_hmac_ret1) {                return STATUS_ERROR;            }            sha_bytes_to_hex64((uint8*)client_server_signature_bytes, client_server_signature_string);
// 调用函数strncmp判断计算的client_server_signature_string和服务端传来的server_signature值是否相等            if (PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_GAUSS_BASE &&                0 != strncmp(conn->server_signature, client_server_signature_string, HMAC_STRING_LENGTH)) {                pwd_to_send = fail_info;  // 不相等,则认证失败            } else {                sha_hex_to_bytes32(stored_key_bytes, stored_key_string);                // 通过stored_key和token计算得到hmac_result                CRYPT_hmac_ret2 = CRYPT_hmac(NID_hmacWithSHA256,                    (GS_UCHAR*)stored_key_bytes,                    STORED_KEY_LENGTH,                    (GS_UCHAR*)token,                    TOKEN_LENGTH,                    (GS_UCHAR*)hmac_result,                    (GS_UINT32*)&hmac_length);
                if (CRYPT_hmac_ret2) {                    return STATUS_ERROR;                }
                sha_hex_to_bytes32(client_key_bytes, client_key_buf);// hmac_result和client_key_bytes异或得到h,然后将其发送给服务端,用于验证客户端                if (XOR_between_password(hmac_result, client_key_bytes, h, HMAC_LENGTH)) {                    return STATUS_ERROR;                }

2.4 Kerberos安全认证

Kerberos是一种基于对称秘钥技术的身份认证协议,开源组件Kerberos可以解决集群内节点或者进程之间的认证问题,即当开启kerberos之后,恶意用户无法仿冒集群内节点或进程来登录数据库系统,只有内部组件才可以持有用于认证的凭证,从而保证通过Kerberos认证,消减了仿冒风险,提升了数据库系统的安全性。Kerberos协议具体交互如图10所示。

图片

图10 Kerberos认证标准交互流程

其中各角色和定义如下,为下文描述方便,均以缩写代替:

表3 Kerberos协议角色

KDC(Key Distribution Center)

Kerberos服务程序

Client

需要访问服务的用户(principal),KDC和Service会对用户的身份进行认证

Service

集成了Kerberos的服务,被访问的服务,需要对客户端进行认证

AS(Authentication Service,认证服务)

AS服务器用于身份的校验, 内部会存储所有的账号信息

TGS(Ticket Granting Service,票据授权服务)

TGT(Ticket-Granting Ticket)票据分发服务

openGauss可在数据库系统部署完毕之后开启Kerberos模式,即Kerberos服务部署在数据库系统机器上,部署过程中会开启Kerberos相关的服务,并派发凭证给集群内部所有的节点,初始化一系列Kerberos需要用到的环境变量,数据库内核中通过调用GSS-API来实现Kebreros标准协议的通信内容,下面给出在Kerberos开启后openGauss内部进程之间认证流程,以openGauss主备之间的认证为例:

图片

图11  数据库系统Kerberos认证流程

Kerberos提供用户(数据库管理员)透明的认证机制,数据库管理员无需感知Kerberos进程/部署情况。分两部分描述Kerberos交互,左侧虚线框内的Kerberos协议实现部分由OM(Operations Management,运维管理模块)工具完成,OM工具在Kerberos初始化的时候将KDC服务拉起(krb5kdc进程),其内置了两个服务:AS和TGS服务,客户端(openGauss主备等数据库服务进程)在登录对端之前会先和KDC交互拿到TGT(Ticket Granting Ticket,根凭证),这个步骤由OM拉起的定时任务调用Kerebros提供刷新票据工具来实现,默认24小时重新获取1次。这个获取TGT的过程对应Kerberos标准协议中AS-REQ、AS-REP、TGS-REQ和TGS-REP。

在数据库内核侧,主要是图12右侧虚线框内的AP-REQ流程实现,简化流程如图12所示。

图片

图12 数据库系统内核认证交互

数据库内核封装GSS-API提供数据结构和API实现认证交互,关键数据结构如下:

​​​​​typedef struct GssConn {int sock;gss_ctx_id_t gctx;        // GSS 上下文gss_name_t gtarg_nam;   // GSS 名称gss_buffer_desc ginbuf;   // GSS 输入tokengss_buffer_desc goutbuf;  // GSS 输出token
} GssConn; 
// 客户端、服务端接口, 用于封装标准kerberos协议调用, 其中客户端接口用于向服务端
// 发起访问,同时响应服务端接口GssServerAuth发起的票据请求
int GssClientAuth(int socket, char* server_host);
int GssServerAuth(int socket, const char* krb_keyfile);

图片

图13 数据库内核Kerberos认证时序图

具体交互逻辑时序如图13所示。

  • 首先服务端通过数据库配置文件决定使用Kerberos协议对客户端连接进行认证。

  • 然后发起认证请求,客户端准备需要Kerberos认证的环境和票证,发’P’报文响应请求并发送票证。

  • 服务端验证通过后会发送响应’R’报文,完成Kerberos认证。

 以上内容从安全整体架构与安全认证两方面,对高斯数据库的高安全性能进行了详细解读,下篇我们将从角色权限方面继续介绍高斯数据库的高安全技术,敬请期待~

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

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

相关文章

linux-23 文件管理(一)创建文件touch,stat

那接下来看看文件的创建和删除&#xff0c;那我们怎么去创建一个文件&#xff1f;各种方式都能实现&#xff0c;当然&#xff0c;这里先说一说&#xff0c;就像mkdir创建空目录一样&#xff0c;我们如何创建一个空文件&#xff1f;创建空文件其实很简单&#xff0c;有一个命令歪…

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…

金蝶V10中间件的使用

目录 环境准备搭建过程配置修改应用部署 环境准备 Linux内核服务器JDK1.8安装包&#xff1a;AAS-V10.zip程序包&#xff1a;***.war 搭建过程 将安装包上传至服务器opt目录下&#xff0c;官方给定的默认服务主目录为“/opt/AAS-V10/ApusicAS/aas/”&#xff1b;解压安装包(解…

前端开发 -- 自动回复机器人【附完整源码】

一&#xff1a;效果展示 本项目实现了一个简单的网页聊天界面&#xff0c;用户可以在输入框中输入消息&#xff0c;并点击发送按钮或按下回车键来发送消息。机器人会根据用户发送的消息内容&#xff0c;通过关键字匹配来生成自动回复。 二&#xff1a;源代码分享 <!DOCTYP…

Python数据可视化小项目

英雄联盟S14世界赛选手数据可视化 由于本学期有一门数据可视化课程&#xff0c;课程结课作业要求完成一个数据可视化的小Demo&#xff0c;于是便有了这个小项目&#xff0c;课程老师要求比较简单&#xff0c;只要求熟练运用可视化工具展示数据&#xff0c;并不要求数据来源&am…

Linux系统编程——详解页表

目录 一、前言 二、深入理解页表 三、页表的实际组成 四、总结&#xff1a; 一、前言 页表是我们之前在讲到程序地址空间的时候说到的&#xff0c;它是物理内存到进程程序地址空间的一个桥梁&#xff0c;通过它物理内存的数据和代码才能映射到进程的程序地址空间中&#xff…

【Java数据结构】LinkedList与链表

认识LinkedList LinkedList就是一个链表&#xff0c;它也是实现List接口的一个类。LinkedList就是通过next引用将所有的结点链接起来&#xff0c;所以不需要数组。LinkedList也是以泛型的方法实现的&#xff0c;所以使用这个类都需要实例化对象。 链表分为很多种&#xff0c;比…

《一文读懂卷积网络CNN:原理、模型与应用全解析》

《一文读懂卷积网络CNN&#xff1a;原理、模型与应用全解析》 一、CNN 基本原理大揭秘&#xff08;一&#xff09;从人类视觉到 CNN 灵感&#xff08;二&#xff09;核心组件详解 二、经典 CNN 模型巡礼&#xff08;一&#xff09;LeNet-5&#xff1a;开山鼻祖&#xff08;二&a…

教育元宇宙的优势与核心功能解析

随着科技的飞速发展&#xff0c;教育领域正迎来一场前所未有的变革。教育元宇宙作为新兴的教育形态&#xff0c;以其独特的优势和丰富的功能&#xff0c;正在逐步改变我们的学习方式。本文将深入探讨教育元宇宙的优势以及其核心功能&#xff0c;为您揭示这一未来教育的新趋势。…

openGauss与GaussDB系统架构对比

openGauss与GaussDB系统架构对比 系统架构对比openGauss架构GaussDB架构 GaussDB集群管理组件 系统架构对比 openGauss架构 openGauss是集中式数据库系统&#xff0c;业务数据存储在单个物理节点上&#xff0c;数据访问任务被推送到服务节点执行&#xff0c;通过服务器的高并…

idea 8年使用整理

文章目录 前言idea 8年使用整理1. 覆盖application配置2. 启动的时候设置编辑空间大小&#xff0c;并忽略最大空间3. 查询类的关系4. 查看这个方法的引用关系5. 查看方法的调用关系5.1. 查看被调用关系5.2. 查看调用关系 6. 方法分隔线7. 选择快捷键类型8. 代码预览插件9. JReb…

C++ OCR 文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

SQL中的窗口函数

1.窗口函数简介 窗口函数是SQL中的一项高级特性&#xff0c;用于在不改变查询结果集行数的情况下&#xff0c;对每一行执行聚合计算或者其他复杂的计算&#xff0c;也就是说窗口函数可以跨行计算&#xff0c;可以扫描所有的行&#xff0c;并把结果填到每一行中。这些函数通常与…

SpringBoot(Ⅱ)——@SpringBootApplication注解+自动装配原理+约定大于配置

1. SpringBootApplication注解 SpringBootApplication标注在某个类上说明这个类是SpringBoot的主配置类&#xff0c;SpringBoot就通过运行这个类的main方法来启动SpringBoot应用&#xff1b; 并且Configuration注解中也有Component注解&#xff0c;所以这个主启动类/主配置类…

音视频入门知识(二)、图像篇

⭐二、图像篇 视频基本要素&#xff1a;宽、高、帧率、编码方式、码率、分辨率 ​ 其中码率的计算&#xff1a;码率(kbps)&#xff1d;文件大小(KB)&#xff0a;8&#xff0f;时间(秒)&#xff0c;即码率和视频文件大小成正比 YUV和RGB可相互转换 ★YUV&#xff08;原始数据&am…

CTFshow—爆破

Web21 直接访问页面的话会弹窗需要输入密码验证&#xff0c;抓个包看看&#xff0c;发现是Authorization认证&#xff0c;Authorization请求头用于验证是否有从服务器访问所需数据的权限。 把Authorization后面的数据进行base64解码&#xff0c;就是我们刚刚输入的账号密码。 …

lin.security提权靶场渗透

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

【魅力golang】之-泛型

早期的golang版本是不支持泛型的&#xff0c;这对于从其它语言转型做go开发的程序员来说&#xff0c;非常不友好&#xff0c;自 1.18开始golang正式支持泛型&#xff0c;解决了开发者在编写通用代码时的需求。泛型通过类型参数允许函数和数据结构支持多种类型&#xff0c;从而提…

数据结构(Java)——链表

1.概念及结构 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 2.分类 链表的结构非常多样&#xff0c;以下情况组合起来就有 8 种链表结构&#xff1a; &#xff08;1&#xff09;单向或者双向 &#xff08;…

pdf有密码,如何实现pdf转换word?

PDF想要转换成其他格式&#xff0c;但是当我们将文件拖到PDF转换器进行转换的时候发现PDF文件带有密码怎么办&#xff1f;今天分享PDF有密码如何转换成word方法。 方法一、 PDF文件有两种密码&#xff0c;打开密码和限制编辑&#xff0c;如果是因为打开密码&#xff0c;建议使…