|
@@ -0,0 +1,311 @@
|
|
|
|
|
+# API Basic Auth 认证指南
|
|
|
|
|
+
|
|
|
|
|
+本文档说明外部服务器如何安全地调用本项目的 API 接口(已配置 Basic Auth)。
|
|
|
|
|
+
|
|
|
|
|
+## 安全原则
|
|
|
|
|
+
|
|
|
|
|
+**⚠️ 重要:永远不要在代码中硬编码用户名和密码!**
|
|
|
|
|
+
|
|
|
|
|
+应该使用以下方式存储凭证:
|
|
|
|
|
+
|
|
|
|
|
+- 环境变量(推荐)
|
|
|
|
|
+- 密钥管理服务(如 AWS Secrets Manager、HashiCorp Vault)
|
|
|
|
|
+- 配置文件(不提交到 Git,使用 `.gitignore`)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 配置方式
|
|
|
|
|
+
|
|
|
|
|
+### 1. 设置环境变量
|
|
|
|
|
+
|
|
|
|
|
+在外部服务器的环境中设置以下变量:
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# Basic Auth 用户名
|
|
|
|
|
+export BYREAL_API_USERNAME=admin
|
|
|
|
|
+
|
|
|
|
|
+# Basic Auth 密码
|
|
|
|
|
+export BYREAL_API_PASSWORD=your_password_here
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+或者在 `.env` 文件中(不要提交到 Git):
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+BYREAL_API_USERNAME=admin
|
|
|
|
|
+BYREAL_API_PASSWORD=your_password_here
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 代码示例
|
|
|
|
|
+
|
|
|
|
|
+### Node.js / TypeScript
|
|
|
|
|
+
|
|
|
|
|
+#### 使用原生 fetch
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 从环境变量读取凭证
|
|
|
|
|
+const username = process.env.BYREAL_API_USERNAME || ''
|
|
|
|
|
+const password = process.env.BYREAL_API_PASSWORD || ''
|
|
|
|
|
+
|
|
|
|
|
+// 生成 Basic Auth header
|
|
|
|
|
+const credentials = Buffer.from(`${username}:${password}`).toString('base64')
|
|
|
|
|
+const authHeader = `Basic ${credentials}`
|
|
|
|
|
+
|
|
|
|
|
+// 发送请求
|
|
|
|
|
+const response = await fetch('http://your-server-ip/api/my-lp', {
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: authHeader,
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const data = await response.json()
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 使用 axios
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+import axios from 'axios'
|
|
|
|
|
+
|
|
|
|
|
+const username = process.env.BYREAL_API_USERNAME || ''
|
|
|
|
|
+const password = process.env.BYREAL_API_PASSWORD || ''
|
|
|
|
|
+
|
|
|
|
|
+const response = await axios.get('http://your-server-ip/api/my-lp', {
|
|
|
|
|
+ auth: {
|
|
|
|
|
+ username,
|
|
|
|
|
+ password,
|
|
|
|
|
+ },
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ },
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const data = response.data
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 使用 ky(类似你项目中的方式)
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+import ky from 'ky'
|
|
|
|
|
+
|
|
|
|
|
+const username = process.env.BYREAL_API_USERNAME || ''
|
|
|
|
|
+const password = process.env.BYREAL_API_PASSWORD || ''
|
|
|
|
|
+
|
|
|
|
|
+const credentials = Buffer.from(`${username}:${password}`).toString('base64')
|
|
|
|
|
+
|
|
|
|
|
+const response = await ky.get('http://your-server-ip/api/my-lp', {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ Authorization: `Basic ${credentials}`,
|
|
|
|
|
+ },
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const data = await response.json()
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Python
|
|
|
|
|
+
|
|
|
|
|
+#### 使用 requests
|
|
|
|
|
+
|
|
|
|
|
+```python
|
|
|
|
|
+import os
|
|
|
|
|
+import requests
|
|
|
|
|
+from requests.auth import HTTPBasicAuth
|
|
|
|
|
+
|
|
|
|
|
+# 从环境变量读取
|
|
|
|
|
+username = os.getenv('BYREAL_API_USERNAME', '')
|
|
|
|
|
+password = os.getenv('BYREAL_API_PASSWORD', '')
|
|
|
|
|
+
|
|
|
|
|
+# 方式 1: 使用 HTTPBasicAuth(推荐)
|
|
|
|
|
+response = requests.get(
|
|
|
|
|
+ 'http://your-server-ip/api/my-lp',
|
|
|
|
|
+ auth=HTTPBasicAuth(username, password),
|
|
|
|
|
+ headers={'Content-Type': 'application/json'}
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+data = response.json()
|
|
|
|
|
+
|
|
|
|
|
+# 方式 2: 手动设置 header
|
|
|
|
|
+import base64
|
|
|
|
|
+credentials = base64.b64encode(f'{username}:{password}'.encode()).decode()
|
|
|
|
|
+headers = {
|
|
|
|
|
+ 'Authorization': f'Basic {credentials}',
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+}
|
|
|
|
|
+response = requests.get('http://your-server-ip/api/my-lp', headers=headers)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### curl 命令
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# 从环境变量读取
|
|
|
|
|
+curl -u "${BYREAL_API_USERNAME}:${BYREAL_API_PASSWORD}" \
|
|
|
|
|
+ http://your-server-ip/api/my-lp
|
|
|
|
|
+
|
|
|
|
|
+# 或者直接指定(不推荐,密码会出现在命令历史中)
|
|
|
|
|
+curl -u "admin:your_password" \
|
|
|
|
|
+ http://your-server-ip/api/my-lp
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Go
|
|
|
|
|
+
|
|
|
|
|
+```go
|
|
|
|
|
+package main
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "encoding/base64"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "io"
|
|
|
|
|
+ "net/http"
|
|
|
|
|
+ "os"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func main() {
|
|
|
|
|
+ username := os.Getenv("BYREAL_API_USERNAME")
|
|
|
|
|
+ password := os.Getenv("BYREAL_API_PASSWORD")
|
|
|
|
|
+
|
|
|
|
|
+ credentials := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
|
|
|
|
+
|
|
|
|
|
+ req, _ := http.NewRequest("GET", "http://your-server-ip/api/my-lp", nil)
|
|
|
|
|
+ req.Header.Set("Authorization", "Basic "+credentials)
|
|
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
+
|
|
|
|
|
+ client := &http.Client{}
|
|
|
|
|
+ resp, err := client.Do(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ panic(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer resp.Body.Close()
|
|
|
|
|
+
|
|
|
|
|
+ body, _ := io.ReadAll(resp.Body)
|
|
|
|
|
+ fmt.Println(string(body))
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Java
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+import java.net.http.HttpClient;
|
|
|
|
|
+import java.net.http.HttpRequest;
|
|
|
|
|
+import java.net.http.HttpResponse;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.util.Base64;
|
|
|
|
|
+
|
|
|
|
|
+public class ApiClient {
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ String username = System.getenv("BYREAL_API_USERNAME");
|
|
|
|
|
+ String password = System.getenv("BYREAL_API_PASSWORD");
|
|
|
|
|
+
|
|
|
|
|
+ String credentials = Base64.getEncoder().encodeToString(
|
|
|
|
|
+ (username + ":" + password).getBytes()
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ HttpClient client = HttpClient.newHttpClient();
|
|
|
|
|
+ HttpRequest request = HttpRequest.newBuilder()
|
|
|
|
|
+ .uri(URI.create("http://your-server-ip/api/my-lp"))
|
|
|
|
|
+ .header("Authorization", "Basic " + credentials)
|
|
|
|
|
+ .header("Content-Type", "application/json")
|
|
|
|
|
+ .GET()
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ HttpResponse<String> response = client.send(request,
|
|
|
|
|
+ HttpResponse.BodyHandlers.ofString());
|
|
|
|
|
+ System.out.println(response.body());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Docker 环境配置
|
|
|
|
|
+
|
|
|
|
|
+如果外部服务器也在 Docker 中运行,在 `docker-compose.yml` 中配置:
|
|
|
|
|
+
|
|
|
|
|
+```yaml
|
|
|
|
|
+services:
|
|
|
|
|
+ your-service:
|
|
|
|
|
+ image: your-image
|
|
|
|
|
+ environment:
|
|
|
|
|
+ - BYREAL_API_USERNAME=${BYREAL_API_USERNAME}
|
|
|
|
|
+ - BYREAL_API_PASSWORD=${BYREAL_API_PASSWORD}
|
|
|
|
|
+ # 或者从 .env 文件读取
|
|
|
|
|
+ env_file:
|
|
|
|
|
+ - .env
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 安全最佳实践
|
|
|
|
|
+
|
|
|
|
|
+1. ✅ **使用环境变量**:永远不要在代码中硬编码密码
|
|
|
|
|
+2. ✅ **使用 `.gitignore`**:确保包含密码的 `.env` 文件不会被提交
|
|
|
|
|
+3. ✅ **使用密钥管理服务**:生产环境推荐使用 AWS Secrets Manager、Azure Key Vault 等
|
|
|
|
|
+4. ✅ **定期轮换密码**:定期更换 Basic Auth 密码
|
|
|
|
|
+5. ✅ **使用 HTTPS**:生产环境必须使用 HTTPS(当前是 HTTP,仅用于开发/内网)
|
|
|
|
|
+6. ✅ **限制访问 IP**:在 Nginx 中配置 `allow/deny` 限制允许访问的 IP
|
|
|
|
|
+7. ✅ **最小权限原则**:只为需要的服务提供凭证
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 测试连接
|
|
|
|
|
+
|
|
|
|
|
+使用 curl 测试 Basic Auth 是否配置正确:
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# 测试(会提示输入密码)
|
|
|
|
|
+curl -u admin http://your-server-ip/api/my-lp
|
|
|
|
|
+
|
|
|
|
|
+# 或者从环境变量读取
|
|
|
|
|
+curl -u "${BYREAL_API_USERNAME}:${BYREAL_API_PASSWORD}" \
|
|
|
|
|
+ http://your-server-ip/api/my-lp
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+如果返回 401 Unauthorized,说明凭证错误;如果返回 200 或其他业务状态码,说明认证成功。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 常见问题
|
|
|
|
|
+
|
|
|
|
|
+### Q: 如何获取 Basic Auth 凭证?
|
|
|
|
|
+
|
|
|
|
|
+A: 联系项目管理员获取用户名和密码。密码存储在服务器的 `htpasswd` 文件中。
|
|
|
|
|
+
|
|
|
|
|
+### Q: 密码忘记了怎么办?
|
|
|
|
|
+
|
|
|
|
|
+A: 在服务器上重新生成:
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+htpasswd ./htpasswd admin
|
|
|
|
|
+# 输入新密码
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+然后重启 Docker 容器:
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+docker compose restart nginx
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Q: 如何在生产环境使用 HTTPS?
|
|
|
|
|
+
|
|
|
|
|
+A: 需要配置 SSL 证书,修改 `nginx.conf` 添加 SSL 配置,并更新 `docker-compose.yml` 映射 443 端口。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 相关文件
|
|
|
|
|
+
|
|
|
|
|
+- `nginx.conf` - Nginx Basic Auth 配置
|
|
|
|
|
+- `htpasswd` - Basic Auth 密码文件(不提交到 Git)
|
|
|
|
|
+- `docker-compose.yml` - Docker 服务配置
|