TCP 套接字+TLS+HTTP 基本认证

在安全敏感和多用户共享的场景中,用户名和密码验证是保障系统安全和数据完整性的重要手段;

在 TCP 传输中实现用户名和密码验证。假设使用的是基于 POSIX 套接字的系统。

务器端:

创建一个 TCP 套接字,并绑定到指定的端口。

监听客户端连接,当有客户端连接时,调用handle_client函数。

handle_client函数接收客户端发送的用户名和密码,与预设的有效用户名和密码进行比对,并返回验证结果。

客户端:

创建一个 TCP 套接字,并连接到服务器。

将用户名和密码以username:password的格式发送到服务器。

接收服务器的验证结果并打印

服务器端

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

// 模拟的用户名和密码
const char *valid_username = "admin";
const char *valid_password = "password123";

void handle_client(int client_socket) {
    char buffer[BUFFER_SIZE] = {0};
    ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_read <= 0) {
        perror("接收数据失败");
        close(client_socket);
        return;
    }
    buffer[bytes_read] = '\0';

    // 打印接收到的数据
    printf("接收到的数据: %s\n", buffer);

    char *username = strtok(buffer, ":");
    char *password = strtok(NULL, ":");

    if (username && password && strcmp(username, valid_username) == 0 && strcmp(password, valid_password) == 0) {
        const char *response = "验证成功";
        send(client_socket, response, strlen(response), 0);
    } else {
        const char *response = "验证失败";
        send(client_socket, response, strlen(response), 0);
    }

    close(client_socket);
}

int main(int argc, char const *argv[]) {
    int server_socket, client_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    // 创建套接字
    if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("套接字创建失败");
        exit(EXIT_FAILURE);
    }

    // 设置套接字选项
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("设置套接字选项失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("绑定失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_socket, MAX_CLIENTS) < 0) {
        perror("监听失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("服务器正在监听端口 %d...\n", PORT);

    while (1) {
        if ((client_socket = accept(server_socket, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
            perror("接受连接失败");
            continue;
        }

        handle_client(client_socket);
    }

    close(server_socket);
    return 0;
}

客户端

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1" // 替换为服务器实际IP地址
#define BUFFER_SIZE 1024

int main(int argc, char const *argv[]) {
    int client_socket;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    const char *username = "admin";
    const char *password = "password123";
    char send_buffer[BUFFER_SIZE];
    const char *data_to_send = "这是连接成功且验证通过后要发送的数据";

    // 创建套接字
    if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("套接字创建失败");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("连接失败");
        close(client_socket);
        exit(EXIT_FAILURE);
    }

    // 发送用户名和密码
    snprintf(send_buffer, sizeof(send_buffer), "%s:%s", username, password);
    send(client_socket, send_buffer, strlen(send_buffer), 0);

    // 接收服务器响应
    ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_read <= 0) {
        perror("接收数据失败");
    } else {
        buffer[bytes_read] = '\0';
        if (strcmp(buffer, "验证成功") == 0) {
            // 验证成功后发送数据
            send(client_socket, data_to_send, strlen(data_to_send), 0);
            printf("已发送数据: %s\n", data_to_send);
        } else {
            printf("服务器响应: %s\n", buffer);
        }
    }

    close(client_socket);
    return 0;
}

进一步了解

在 TCP 传输数据时使用用户名和密码与使用 TLS 存在显著区别

身份验证层面

用户名和密码:基于应用层实现身份验证,依赖应用自身维护用户信息数据库。发送方将用户名和密码随数据一同发送给接收方,接收方通过查询本地数据库或其他认证服务来验证其合法性。这种方式下,用户名和密码在网络中传输,若传输过程被截取,攻击者可能获取并利用这些信息进行非法访问。

TLS:TLS 在传输层提供身份验证,采用数字证书机制。服务器向客户端发送数字证书,客户端使用内置的根证书来验证服务器证书的合法性,确认服务器身份真实可靠。同时,若开启双向认证,客户端也需向服务器提供数字证书,完成双方身份验证。此过程不依赖应用自身的用户信息,安全性更高。

数据保护层面

用户名和密码:仅验证用户身份,对传输的其他数据无加密保护。若数据敏感,还需在应用层另行加密,增加开发复杂性。

TLS:对整个 TCP 连接中的数据进行加密传输,包括握手阶段交换的信息和后续传输的应用数据。通过对称加密算法对数据加密,密钥通过非对称加密算法安全交换,防止数据被窃取、篡改或中间人攻击。

部署和维护层面

用户名和密码:应用开发者需自行设计、实现和维护用户注册、登录、密码重置等功能,以及用户信息存储和管理系统,工作繁琐且易出现安全漏洞,如密码存储不安全、认证逻辑缺陷等。

TLS:依赖证书颁发机构(CA)颁发和管理数字证书。虽然部署需获取和配置证书,但大多数情况下可复用成熟的 TLS 库和工具,减少开发工作量。后续证书更新、吊销等维护工作由 CA 负责,开发者只需关注证书有效期和更新提醒。

TLS 解释

TLS(传输层安全协议)是基于 TCP 协议之上,为网络通信提供安全及数据完整性的协议。它通过在客户端和服务器之间建立加密通道,确保数据在传输过程中的保密性、完整性和身份验证。TLS 协议主要包括以下几个阶段:

握手阶段:客户端与服务器交换信息,协商加密算法、生成共享密钥,并进行身份验证。此阶段会验证服务器数字证书,若双向认证,服务器也会验证客户端证书。

加密通信阶段:握手成功后,双方使用协商好的加密算法和密钥对传输数据加密。数据在发送端加密,接收端解密,确保数据在传输过程中不被窃取或篡改。

安装

bash 复制代码
sudo apt-get install libssl-dev

生成server.crt

使用 OpenSSL 生成server.crt(服务器证书)通常需要以下步骤,以下示例基于 Linux 系统,生成一个自签名的证书,在实际生产环境中,建议从受信任的证书颁发机构(CA)获取证书

生成私钥

首先,需要生成一个私钥文件,一般使用 RSA 算法。

bash 复制代码
openssl genpkey -algorithm RSA -out server.key

这条命令会在当前目录下生成一个名为server.key的私钥文件。执行过程中,OpenSSL 会生成一个 RSA 私钥,并将其保存到指定的文件中。

生成证书签名请求(CSR)

有了私钥后,接下来生成证书签名请求(CSR)。CSR 包含了服务器的相关信息,如域名、组织等,用于向 CA 请求证书。

bash 复制代码
openssl req -new -key server.key -out server.csr

执行该命令后,会提示你输入一些信息:

Country Name (2 letter code) []: 国家代码,例如 CN 代表中国。

State or Province Name (full name) []: 州或省份名称,如 Guangdong。

Locality Name (eg, city) []: 地区名称,如 Shenzhen。

Organization Name (eg, company) []: 组织名称,如 MyCompany。

Organizational Unit Name (eg, section) []: 组织单位名称,可留空。

Common Name (eg, fully qualified host name) []: 通用名称,通常是服务器的域名,如果是在本地测试,可以使用 localhost。

Email Address []: 电子邮件地址,可留空。

自签名生成服务器证书(server.crt)

由于是生成自签名证书,我们使用刚才生成的 CSR 和私钥来创建server.crt文件。

bash 复制代码
openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365

-days 365 参数指定证书的有效期为 365 天,你可以根据需要修改这个值。

执行上述命令后,当前目录下会生成一个名为server.crt的自签名服务器证书文件。

查看证书信息

bash 复制代码
openssl x509 -noout -text -in server.crt

服务器端实现 TLS 加密

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

// 模拟的用户名和密码
const char *valid_username = "admin";
const char *valid_password = "password123";

void handle_client(int client_socket, SSL *ssl) {
    char buffer[BUFFER_SIZE] = {0};
    ssize_t bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
    if (bytes_read <= 0) {
        ERR_print_errors_fp(stderr);
        SSL_shutdown(ssl);
        close(client_socket);
        return;
    }
    buffer[bytes_read] = '\0';

    // 打印接收到的数据
    printf("接收到的数据: %s\n", buffer);

    char *username = strtok(buffer, ":");
    char *password = strtok(NULL, ":");

    if (username && password && strcmp(username, valid_username) == 0 && strcmp(password, valid_password) == 0) {
        const char *response = "验证成功";
        SSL_write(ssl, response, strlen(response));
    } else {
        const char *response = "验证失败";
        SSL_write(ssl, response, strlen(response));
    }

    SSL_shutdown(ssl);
    close(client_socket);
}

int main(int argc, char const *argv[]) {
    int server_socket, client_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    // 初始化OpenSSL
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 创建SSL_CTX对象
    SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        return 1;
    }

    // 加载服务器证书和私钥
    if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        SSL_CTX_free(ctx);
        return 1;
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        SSL_CTX_free(ctx);
        return 1;
    }

    // 检查私钥是否与证书匹配
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "私钥与证书不匹配\n");
        SSL_CTX_free(ctx);
        return 1;
    }

    // 创建套接字
    if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("套接字创建失败");
        exit(EXIT_FAILURE);
    }

    // 设置套接字选项
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("设置套接字选项失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(server_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("绑定失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_socket, MAX_CLIENTS) < 0) {
        perror("监听失败");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("服务器正在监听端口 %d...\n", PORT);

    while (1) {
        if ((client_socket = accept(server_socket, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {
            perror("接受连接失败");
            continue;
        }

        // 创建SSL对象并绑定到套接字
        SSL *ssl = SSL_new(ctx);
        SSL_set_fd(ssl, client_socket);

        // 进行TLS握手
        if (SSL_accept(ssl) <= 0) {
            ERR_print_errors_fp(stderr);
            SSL_shutdown(ssl);
            close(client_socket);
            continue;
        }

        handle_client(client_socket, ssl);
        SSL_free(ssl);
    }

    SSL_CTX_free(ctx);
    close(server_socket);
    return 0;
}

编译

bash 复制代码
gcc -o server server.c -lssl -lcrypto

初始化 OpenSSL:在main函数开始时,调用SSL_library_init、OpenSSL_add_all_algorithms和SSL_load_error_strings初始化 OpenSSL 库。

创建 SSL_CTX 对象:使用SSL_CTX_new并指定TLS_server_method创建一个 SSL 上下文对象。

加载证书和私钥:通过SSL_CTX_use_certificate_file和SSL_CTX_use_PrivateKey_file加载服务器证书和私钥,并使用SSL_CTX_check_private_key检查私钥与证书是否匹配。

创建和配置套接字:与原始代码相同,创建并配置 TCP 套接字。

处理客户端连接:在while循环中,接受客户端连接后,创建一个 SSL 对象并将其绑定到客户端套接字,然后进行 TLS 握手。如果握手成功,调用handle_client函数处理客户端请求,该函数使用SSL_read和SSL_write进行数据的读取和写入。

清理资源:在程序结束时,释放 SSL 上下文和相关资源。

客户器端实现 TLS 加密

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1" // 替换为服务器实际IP地址
#define BUFFER_SIZE 1024

int main(int argc, char const *argv[]) {
    int client_socket;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    const char *username = "admin";
    const char *password = "password123";
    char send_buffer[BUFFER_SIZE];
    const char *data_to_send = "这是连接成功且验证通过后要发送的数据";

    // 初始化OpenSSL
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 创建SSL_CTX对象
    SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
    if (!ctx) {
        ERR_print_errors_fp(stderr);
        return 1;
    }

    // 加载CA证书(用于验证服务器证书)
    if (SSL_CTX_load_verify_locations(ctx, "server.crt", NULL) != 1) {
        ERR_print_errors_fp(stderr);
        SSL_CTX_free(ctx);
        return 1;
    }

    // 创建套接字
    if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("套接字创建失败");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("连接失败");
        close(client_socket);
        exit(EXIT_FAILURE);
    }

    // 创建SSL对象并绑定到套接字
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, client_socket);

    // 进行TLS握手
    if (SSL_connect(ssl) != 1) {
        ERR_print_errors_fp(stderr);
        SSL_free(ssl);
        SSL_CTX_free(ctx);
        close(client_socket);
        return 1;
    }

    // 发送用户名和密码
    snprintf(send_buffer, sizeof(send_buffer), "%s:%s", username, password);
    SSL_write(ssl, send_buffer, strlen(send_buffer));

    // 接收服务器响应
    ssize_t bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
    if (bytes_read <= 0) {
        ERR_print_errors_fp(stderr);
    } else {
        buffer[bytes_read] = '\0';
        if (strcmp(buffer, "验证成功") == 0) {
            // 验证成功后发送数据
            SSL_write(ssl, data_to_send, strlen(data_to_send));
            printf("已发送数据: %s\n", data_to_send);
        } else {
            printf("服务器响应: %s\n", buffer);
        }
    }

    // 清理资源
    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(client_socket);
    return 0;
}

初始化 OpenSSL:在main函数开始处,调用SSL_library_init、OpenSSL_add_all_algorithms和SSL_load_error_strings初始化 OpenSSL 库。

创建 SSL_CTX 对象:使用SSL_CTX_new并指定TLS_client_method创建一个适用于客户端的 SSL 上下文对象。

加载 CA 证书:通过SSL_CTX_load_verify_locations加载 CA 证书,用于验证服务器发送的证书。这里假设ca.crt是包含服务器证书颁发机构公钥的证书文件。

创建和连接套接字:与原始代码类似,创建 TCP 套接字并连接到服务器。

创建 SSL 对象并握手:创建一个 SSL 对象并将其绑定到客户端套接字,然后调用SSL_connect进行 TLS 握手。如果握手失败,打印错误信息并清理资源。

数据交互:握手成功后,使用SSL_write发送用户名和密码,使用SSL_read接收服务器响应。验证成功后,再次使用SSL_write发送后续数据。

清理资源:通信结束后,调用SSL_shutdown关闭 SSL 连接,然后释放 SSL 对象、SSL 上下文并关闭套接字。

编译

bash 复制代码
gcc -o client client.c -lssl -lcrypto

HTTP 基本认证(Basic Authentication)

原理

用户凭证编码:客户端将用户名和密码用冒号(:)连接,形成字符串username:password,然后对该字符串进行 Base64 编码。Base64 编码是一种将二进制数据转换为可打印 ASCII 字符的编码方式,主要用于在文本协议中传输二进制数据。

请求头携带:编码后的字符串被放置在 HTTP 请求头的Authorization字段中,格式为Basic <encoded_credentials>。例如,如果用户名是admin,密码是password123,经过 Base64 编码后得到YWRtaW46cGFzc3dvcmQxMjM=,那么Authorization字段将是Basic YWRtaW46cGFzc3dvcmQxMjM=。

服务器验证:服务器接收到请求后,提取Authorization头字段,解析出 Base64 编码的部分并进行解码,还原出原始的username:password字符串。然后,服务器将解码后的用户名和密码与存储的有效凭证进行对比,以验证用户身份。

工作流程

客户端请求资源:客户端向服务器发送对受保护资源的 HTTP 请求。

服务器要求认证:如果请求未包含有效的Authorization头,服务器返回 HTTP 401 Unauthorized 响应,并在响应头中包含WWW - Authenticate字段,指示客户端需要进行认证。WWW - Authenticate字段通常包含认证方案(如Basic)和可能的其他参数(如realm,用于指定认证领域,给用户提示需要输入的凭据类型,例如realm="Restricted Area")。

客户端提供凭证:客户端收到 401 响应后,提示用户输入用户名和密码。用户输入后,客户端按照上述原理进行 Base64 编码,并将编码后的凭证放在新的 HTTP 请求的Authorization头中,再次发送请求。

服务器验证凭证:服务器接收到包含Authorization头的请求后,进行解码和验证。如果凭证有效,服务器返回请求的资源;如果无效,服务器再次返回 401 Unauthorized 响应。

优点

简单易用:实现相对简单,无论是服务器端还是客户端,都不需要复杂的代码逻辑。在许多编程语言和 Web 框架中都有现成的库或工具支持基本认证。

广泛支持:被所有主流的 Web 浏览器和服务器软件支持,是一种通用的认证机制,兼容性强。

缺点

安全性有限:Base64 编码并非加密,只是一种编码方式。如果网络通信没有加密(如使用 HTTP 而非 HTTPS),用户名和密码很容易被截取和解码,导致安全风险。即使使用 HTTPS 加密通信,由于每次请求都携带凭证,一旦凭证泄露,攻击者可以直接使用这些凭证访问受保护资源。

缺乏灵活性:基本认证没有提供内置的密码过期、多因素认证等功能。若要实现这些高级安全特性,需要在应用层额外开发。

用户体验欠佳:每次认证失败,浏览器都会弹出标准的认证对话框,这种体验可能不太友好,尤其是对于非技术用户。而且,客户端通常没有很好的方式来缓存凭证,可能导致用户频繁输入凭证。

安全性考虑

结合 HTTPS:为了弥补基本认证本身的安全缺陷,强烈建议在使用 HTTP 基本认证时,结合 HTTPS 协议。HTTPS 通过 SSL/TLS 加密通信内容,确保用户名和密码在传输过程中不被窃取或篡改。

密码存储安全:服务器端存储的用户密码应采用安全的方式,如使用哈希函数(如 bcrypt、scrypt 等)进行加盐哈希处理,而不是明文存储。这样即使数据库泄露,攻击者也难以获取原始密码。

限制访问频率:为防止暴力破解攻击,可以在服务器端对认证请求的频率进行限制,例如限制同一 IP 地址在一定时间内的认证尝试次数。

libmicrohttpd库

简介:libmicrohttpd是一个轻量级的 C 库,用于在程序中嵌入 HTTP 服务器功能。它旨在方便开发者在自己的应用程序中快速搭建 HTTP 服务器,处理 HTTP 请求和响应,而无需依赖外部的大型 Web 服务器软件(如 Apache 或 Nginx)。

特点:

轻量级:占用资源少,适合资源有限的环境,如嵌入式系统。

易于嵌入:可以很方便地集成到现有的 C 语言项目中,通过简单的回调函数机制来处理 HTTP 请求。

支持多种特性:支持 HTTP/1.0 和 HTTP/1.1 协议,支持 HTTP 认证(如基本认证、摘要认证),支持多线程处理请求,支持 HTTP 持久连接等。

应用场景:

小型 Web 服务:用于开发一些小型的、特定用途的 Web 服务,如物联网设备中的配置管理接口、智能家居设备的控制接口等。

测试工具:可用于编写 HTTP 服务器端的测试工具,模拟真实的 HTTP 服务器行为,方便对客户端进行测试。

libcurl库

简介:libcurl是一个功能强大的开源库,用于在程序中进行各种网络协议的客户端操作,支持包括 HTTP、HTTPS、FTP、SMTP、POP3 等多种协议。它提供了统一的接口来处理不同协议的网络请求,使得开发者可以轻松地在自己的应用程序中实现网络数据的传输。

特点:

多协议支持:支持广泛的网络协议,几乎涵盖了所有常见的网络应用场景。

易于使用:提供了简单直观的 API,通过设置各种选项即可完成复杂的网络操作,如设置请求头、认证信息、代理服务器等。

高性能:经过优化,能够高效地处理大量的网络请求,适用于对性能要求较高的应用程序。

可移植性:可以在多种操作系统上使用,包括 Linux、Windows、Mac OS 等。

应用场景:

网络爬虫:用于编写网络爬虫程序,从网页上抓取数据。

数据同步:在应用程序中实现与远程服务器的数据同步功能,如上传或下载文件、发送和接收 JSON 数据等。

API 调用:调用第三方 Web API,获取数据或执行操作,例如调用地图 API 获取地理位置信息、调用云存储 API 进行文件管理等。

服务器端使用libmicrohttpd库,客户端使用libcurl库

服务器端代码

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <microhttpd.h>

#define PORT 8080
#define USERNAME "admin"
#define PASSWORD "password123"

int authenticate(const char *auth_header) {
    if (strncmp(auth_header, "Basic ", 6) != 0) {
        return 0;
    }

    const char *encoded_credentials = auth_header + 6;
    size_t len = strlen(encoded_credentials);
    char *decoded_credentials = (char *)malloc(len + 1);
    if (!decoded_credentials) {
        return 0;
    }

    size_t decoded_len = 0;
    if (base64_decode(encoded_credentials, len, decoded_credentials, &decoded_len) != 0) {
        free(decoded_credentials);
        return 0;
    }
    decoded_credentials[decoded_len] = '\0';

    char *username = strtok(decoded_credentials, ":");
    char *password = strtok(NULL, ":");

    int result = (username && password && strcmp(username, USERNAME) == 0 && strcmp(password, PASSWORD) == 0);

    free(decoded_credentials);
    return result;
}

int basic_auth_callback(void *cls, struct MHD_Connection *connection,
                        const char *url, const char *method,
                        const char *version, const char *upload_data,
                        size_t *upload_data_size, void **ptr) {
    const char *auth_header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Authorization");
    printf("接收到的 Authorization 头信息: %s\n", auth_header? auth_header : "无");

    if (!auth_header ||!authenticate(auth_header)) {
        struct MHD_Response *response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
        int ret = MHD_queue_response(connection, MHD_HTTP_UNAUTHORIZED, response);
        MHD_destroy_response(response);
        return ret;
    }

    struct MHD_Response *response = MHD_create_response_from_buffer(strlen("认证成功"), "认证成功", MHD_RESPMEM_PERSISTENT);
    int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
    MHD_destroy_response(response);
    return ret;
}

int base64_decode(const char *input, size_t input_len, char *output, size_t *output_len) {
    const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    size_t i, j;
    uint32_t value = 0;
    int shift = -6;

    for (i = j = 0; i < input_len; ++i) {
        const char *p = strchr(base64_chars, input[i]);
        if (!p) {
            if (input[i] == '=') {
                if (shift > 0) {
                    break;
                }
            } else {
                return -1;
            }
        } else {
            value = (value << 6) | (p - base64_chars);
            shift += 6;
            if (shift >= 0) {
                output[j++] = (value >> shift) & 0xff;
                shift -= 8;
            }
        }
    }

    if (shift > 0) {
        output[j++] = (value << (8 + shift)) & 0xff;
    }

    *output_len = j;
    return 0;
}

int main() {
    struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL,
                                                 &basic_auth_callback, NULL, MHD_OPTION_END);
    if (daemon == NULL) {
        return 1;
    }

    printf("服务器正在监听端口 %d...\n", PORT);
    getchar();
    MHD_stop_daemon(daemon);
    return 0;
}

客户端代码

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define USERNAME "admin"
#define PASSWORD "password123"

static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    char *buffer = (char *)userp;
    snprintf(buffer, realsize + 1, "%.*s", (int)realsize, (char *)contents);
    return realsize;
}

int main() {
    CURL *curl = curl_easy_init();
    if (curl) {
        char auth[256];
        snprintf(auth, sizeof(auth), "%s:%s", USERNAME, PASSWORD);
        char *encoded_auth = curl_easy_escape(curl, auth, 0);
        char auth_header[256];
        snprintf(auth_header, sizeof(auth_header), "Authorization: Basic %s", encoded_auth);

        curl_free(encoded_auth);

        curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:8080");
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, auth_header);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        char response[1024] = {0};
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);

        CURLcode res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        } else {
            printf("服务器响应: %s\n", response);
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
    return 0;
}
相关推荐
爱学java的ptt5 小时前
TCP详解
网络·网络协议·tcp/ip
RisunJan5 小时前
Linux命令-htpasswd命令(创建和管理用于 HTTP 基本认证(Basic Authentication)的密码文件)
linux·运维·http
阿拉伯柠檬5 小时前
传输层协议TCP(一)
linux·网络·网络协议·tcp/ip·面试
wanzhong23337 小时前
开发日记2-创建http文件测试http接口
网络·网络协议·http
YJlio7 小时前
PsPing 学习笔记(14.2):TCP Ping——端口连通性与服务在线性秒级体检
笔记·学习·tcp/ip
better_liang17 小时前
每日Java面试场景题知识点之-TCP/IP协议栈与Socket编程
java·tcp/ip·计算机网络·网络编程·socket·面试题
sweet丶20 小时前
DNS安全威胁:从劫持、污染到放大攻击的演练
网络协议·安全
小南知更鸟1 天前
前端静态项目快速启动:python -m http.server 4173 与 npx serve . 全解析
前端·python·http
科技块儿1 天前
电商风控实战:如何利用访客IP防控有效识别刷d行为?
大数据·网络协议·tcp/ip