增加服务端验证
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Go / Build (push) Has been cancelled

This commit is contained in:
jiangcuo 2025-04-01 22:12:24 +08:00
parent f72a4416e3
commit 7ac73b9489
8 changed files with 554 additions and 3 deletions

119
README.md
View File

@ -434,3 +434,122 @@ flexibility.
# Acknowledgements
* This product includes software developed by the Thomson Reuters Global Resources. ([go-ntlm](https://github.com/m7913d/go-ntlm) - BSD-4 License)
# RDPGW 认证 API 服务器
这是一个用于 RDPGW远程桌面网关的认证 API 服务器,提供用户验证和密码获取功能。
## 功能特性
- 支持用户名和密码验证verify 模式)
- 支持密码检索功能getpassword 模式),用于 NTLM 认证
- 支持 GET 和 POST 请求方式
- 可通过配置文件自定义设置
## 安装
确保已安装 Node.js v10 或更高版本,然后执行:
```bash
npm install express
```
## 配置
配置文件为 `config.json`,包含以下选项:
```json
{
"port": 3000,
"apiPath": "/api/checkperm",
"users": {
"testuser": "testpassword",
"admin": "adminpass"
},
"logger": {
"level": "info",
"logToFile": false,
"logFile": "server.log"
}
}
```
### 配置选项说明
- `port`: 服务器监听端口
- `apiPath`: API 路径
- `users`: 用户名和密码字典
- `logger`: 日志配置
## 使用方法
### 启动服务器
```bash
node index.js
```
### API 请求示例
#### 验证模式 (verify)
**GET 请求**:
```
http://localhost:3000/api/checkperm?username=testuser&password=testpassword&mode=verify
```
**POST 请求**:
```bash
curl -X POST http://localhost:3000/api/checkperm \
-H "Content-Type: application/json" \
-d '{"username":"testuser", "password":"testpassword", "mode":"verify"}'
```
**响应**:
```json
{
"status": "success",
"user": "testuser"
}
```
#### 密码获取模式 (getpassword)
**GET 请求**:
```
http://localhost:3000/api/checkperm?username=testuser&mode=getpassword
```
**POST 请求**:
```bash
curl -X POST http://localhost:3000/api/checkperm \
-H "Content-Type: application/json" \
-d '{"username":"testuser", "mode":"getpassword"}'
```
**响应**:
```json
{
"status": "success",
"password": "testpassword"
}
```
## RDPGW 集成
在 RDPGW 配置中添加以下内容:
```yaml
ntlm_api:
enable: true
server: http://localhost:3000
path: /api/checkperm
mode: getpassword
```
## 安全建议
- 在生产环境中,请使用 HTTPS 而非 HTTP
- 限制 API 服务器的访问权限
- 定期更换密码并审查访问日志
- 考虑实现 IP 访问限制和请求频率限制

View File

@ -133,8 +133,21 @@ func main() {
if err != nil {
log.Fatal(err)
}
server := grpc.NewServer()
db := database.NewConfig(conf.Users)
// 根据配置选择使用API认证或本地配置认证
var db database.Database
log.Printf("使用API认证API URL: %s", conf.ApiAuth.ApiUrl)
log.Printf("使用API认证API URL: %s", conf.ApiAuth.Enabled)
if conf.ApiAuth.Enabled && conf.ApiAuth.ApiUrl != "" {
log.Printf("使用API认证API URL: %s", conf.ApiAuth.ApiUrl)
db = database.NewApiDb(conf.ApiAuth.ApiUrl)
} else {
log.Printf("使用本地配置文件认证")
db = database.NewConfig(conf.Users)
}
service := NewAuthService(opts.ServiceName, db)
auth.RegisterAuthenticateServer(server, service)
server.Serve(listener)

View File

@ -11,6 +11,12 @@ import (
type Configuration struct {
Users []UserConfig `koanf:"users"`
ApiAuth ApiAuthConfig `koanf:"apiauth"`
}
type ApiAuthConfig struct {
Enabled bool `koanf:"enabled"`
ApiUrl string `koanf:"apiurl"`
}
type UserConfig struct {
@ -36,6 +42,7 @@ func Load(configFile string) Configuration {
koanfTag := koanf.UnmarshalConf{Tag: "koanf"}
k.UnmarshalWithConf("Users", &Conf.Users, koanfTag)
k.UnmarshalWithConf("ApiAuth", &Conf.ApiAuth, koanfTag)
return Conf

169
cmd/auth/database/apidb.go Normal file
View File

@ -0,0 +1,169 @@
package database
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
)
// ApiDb 结构实现Database接口通过API验证用户凭据
type ApiDb struct {
ApiUrl string // API URL模板使用{username}和{password}作为占位符
}
// NewApiDb 创建一个新的ApiDb实例
func NewApiDb(apiUrl string) *ApiDb {
return &ApiDb{
ApiUrl: apiUrl,
}
}
// GetPassword 从API获取用户密码
// 这个方法会调用API来获取用户的实际密码用于NTLM认证
func (a *ApiDb) GetPassword(username string) string {
log.Printf("Getpassword: %s", username)
// 如果用户名为空,直接返回失败
if username == "" {
log.Printf("API password retrieval failed: empty username")
return ""
}
// 构建API URL替换占位符
apiUrl := a.ApiUrl
// 构建完整的URL包括查询参数 - 使用getpassword模式
fullUrl := fmt.Sprintf("%s?username=%s&mode=getpassword",
apiUrl, url.QueryEscape(username))
log.Printf("Sending API password request to: %s", fullUrl)
// 创建自定义HTTP客户端跳过SSL证书验证
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
// 发送请求到API使用不验证SSL证书的客户端
resp, err := client.Get(fullUrl)
if err != nil {
log.Printf("API password retrieval error: %v", err)
return ""
}
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
log.Printf("API password retrieval failed with status: %d", resp.StatusCode)
return ""
}
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to read API response: %v", err)
return ""
}
log.Printf("API password response received")
// 解析响应
var result struct {
Status string `json:"status"`
Password string `json:"password"`
}
err = json.NewDecoder(bytes.NewReader(body)).Decode(&result)
if err != nil {
log.Printf("Failed to parse API response: %v", err)
return ""
}
// 检查响应内容
if result.Status != "success" || result.Password == "" {
log.Printf("API did not return a valid password for user: %s", username)
return ""
}
log.Printf("API password retrieval successful for user: %s", username)
return result.Password
}
// VerifyCredentials 验证用户凭据
func (a *ApiDb) VerifyCredentials(username, password string) bool {
// 如果用户名为空,直接返回失败
if username == "" {
log.Printf("API verification failed: empty username")
return false
}
// 构建API URL替换占位符
apiUrl := a.ApiUrl
// 构建完整的URL包括查询参数
var fullUrl string
if password == "" {
// NTLM场景下直接将用户名传递给API
// 这种情况下后端API应当能够独立验证用户
fullUrl = fmt.Sprintf("%s?username=%s&mode=verify",
apiUrl, url.QueryEscape(username))
log.Printf("Verifying NTLM user via API: %s", username)
} else {
// 常规场景
fullUrl = fmt.Sprintf("%s?username=%s&password=%s&mode=verify",
apiUrl, url.QueryEscape(username), url.QueryEscape(password))
}
log.Printf("Sending API verification request to: %s", fullUrl)
// 创建自定义HTTP客户端跳过SSL证书验证
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
// 发送请求到API使用不验证SSL证书的客户端
resp, err := client.Get(fullUrl)
if err != nil {
log.Printf("API verification error: %v", err)
return false
}
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
log.Printf("API verification failed with status: %d", resp.StatusCode)
return false
}
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Printf("Failed to read API response: %v", err)
return false
}
log.Printf("API response: %s", string(body))
// 解析响应
var result map[string]interface{}
err = json.NewDecoder(bytes.NewReader(body)).Decode(&result)
if err != nil {
log.Printf("Failed to parse API response: %v", err)
return false
}
// 检查响应内容
// 如果响应包含"success",则验证成功
if status, ok := result["status"].(string); ok && status == "success" {
log.Printf("API verification successful for user: %s", username)
return true
}
// 如果没有找到预期的成功标识,则验证失败
log.Printf("API verification failed for user: %s", username)
return false
}

View File

@ -140,13 +140,16 @@ func (c *ntlmContext) authenticate(am *ntlm.AuthenticateMessage, r *auth.NtlmRes
}
username := am.UserName.String()
password := c.h.Database.GetPassword (username)
log.Printf("NTLM: 尝试验证用户: %s", username)
password := c.h.Database.GetPassword(username)
if password == "" {
log.Printf("NTLM: unknown username specified: %s", username)
return nil
}
c.session.SetUserInfo(username,password,"")
log.Printf("NTLM: 成功获取到用户 %s 的密码", username)
c.session.SetUserInfo(username, password, "")
err := c.session.ProcessAuthenticateMessage(am)
if err != nil {
@ -156,5 +159,6 @@ func (c *ntlmContext) authenticate(am *ntlm.AuthenticateMessage, r *auth.NtlmRes
r.Authenticated = true
r.Username = username
log.Printf("NTLM: 用户 %s 认证成功", username)
return nil
}

84
docs/api-auth.md Normal file
View File

@ -0,0 +1,84 @@
# NTLM认证API集成
RDPGW支持通过API集成NTLM认证允许使用您自己的用户管理系统进行认证。
## API模式
API支持两种模式
1. **验证模式verify**:验证用户凭据是否有效
2. **密码获取模式getpassword**获取用户的明文密码用于NTLM挑战-响应计算
## API要求
### 1. 验证模式
用于验证用户凭据是否有效。
**请求格式**
```
GET https://your-api-server/api/checkperm/?username=<用户名>&password=<密码>&mode=verify
```
**成功响应**
```json
{
"status": "success"
}
```
### 2. 密码获取模式
用于获取用户的明文密码这是NTLM认证所必需的。
**请求格式**
```
GET https://your-api-server/api/checkperm/?username=<用户名>&mode=getpassword
```
**成功响应**
```json
{
"status": "success",
"password": "用户的明文密码"
}
```
## 配置
在`rdpgw-auth.yaml`中配置API认证
```yaml
apiauth:
enabled: true
apiurl: "https://your-api-server/api/checkperm/"
```
## 安全考虑
1. **密码安全**API必须通过HTTPS提供并且在内部网络中运行以确保安全性
2. **密码存储**您的API服务必须能够以某种形式获取或计算用户密码这需要安全的密码存储机制
3. **替代方案**如果不想暴露明文密码请考虑使用其他认证方式如OpenID Connect或基本认证
## NTLM认证流程
1. 客户端如FreeRDP发送NTLM协商消息到RDPGW
2. RDPGW生成挑战并发送回客户端
3. 客户端计算响应并发送认证消息
4. RDPGW调用API以`getpassword`模式获取用户密码
5. RDPGW使用获取的密码计算期望的响应并与客户端响应比较
6. 如果匹配,认证成功;否则,认证失败
## 优势
- 保持标准NTLM认证流程客户端无需修改
- 与您的用户管理系统集成
- 支持所有NTLM客户端包括标准Windows远程桌面客户端
## 注意事项
- 您的API必须能够安全地存储和提供用户密码
- 明文密码传输存在固有的安全风险,即使在加密通道中也是如此
- 确保API服务器仅对RDPGW服务器可访问并考虑实施IP限制或类似措施

View File

@ -0,0 +1,23 @@
# RDPGW认证配置示例
# 将此文件保存为rdpgw-auth.yaml并根据您的环境调整设置
# API认证配置
ApiAuth:
# 启用API认证
enabled: true
# API URL - 验证用户凭据的端点
# NTLM认证需要API提供明文密码
# 此API需要支持以下两种模式
# 1. ?username=user&mode=getpassword - 返回用户的明文密码
# 2. ?username=user&password=pass&mode=verify - 验证凭据是否正确
apiurl: "https://your-api-server/api/checkperm/"
# 用户配置
# 当使用API认证时此部分可以为空
# 如果为特定用户提供密码将绕过API并使用本地密码
Users: []
# 或者您也可以同时支持API认证和本地用户认证
# 在这种情况下,请提供本地用户凭据
# users:
# - {Username: "local_user", Password: "local_password"}

132
index.js Normal file
View File

@ -0,0 +1,132 @@
const express = require('express');
const fs = require('fs');
const path = require('path');
// 配置信息
const config = {
port: 3000,
// 用户信息,可以通过配置文件覆盖
users: {
'testuser': 'testpassword',
'admin': 'adminpass',
'user1': 'password1'
},
// API路径
apiPath: '/api/checkperm'
};
// 尝试加载配置文件
try {
const configPath = path.join(__dirname, 'config.json');
if (fs.existsSync(configPath)) {
const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
// 合并配置
Object.assign(config, fileConfig);
console.log('已加载配置文件');
}
} catch (error) {
console.log('加载配置文件失败,使用默认配置:', error.message);
}
const app = express();
// 添加中间件解析JSON和表单数据
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 处理认证逻辑的函数
function handleAuth(username, password, mode) {
// 验证参数是否存在
if (!username) {
console.log('缺少必要参数: username');
return { status: 400, response: { status: 'error', message: '缺少必要参数: username' } };
}
// 检查用户是否存在
if (!config.users[username]) {
console.log(`用户不存在: ${username}`);
return { status: 401, response: { status: 'error', message: '用户不存在' } };
}
// 根据模式处理请求
if (mode === 'getpassword') {
// 密码获取模式 - 用于NTLM认证
console.log(`返回用户 ${username} 的密码`);
return {
status: 200,
response: {
status: 'success',
password: config.users[username]
}
};
} else {
// 默认为verify模式 - 验证用户名和密码
if (!password) {
console.log('缺少必要参数: password');
return { status: 400, response: { status: 'error', message: '缺少必要参数: password' } };
}
if (config.users[username] === password) {
console.log('认证成功');
return {
status: 200,
response: {
status: 'success',
user: username
}
};
} else {
console.log('认证失败: 密码不正确');
return { status: 401, response: { status: 'error', message: '认证失败' } };
}
}
}
// 认证API端点 - GET
app.get(config.apiPath, (req, res) => {
const { username, password, mode } = req.query;
console.log('收到GET认证请求:');
console.log(`username: ${username}`);
console.log(`mode: ${mode || 'verify'}`);
if (password) {
console.log(`password: ${'*'.repeat(password ? password.length : 0)}`); // 为安全起见不打印实际密码
}
const result = handleAuth(username, password, mode);
return res.status(result.status).json(result.response);
});
// 认证API端点 - POST
app.post(config.apiPath, (req, res) => {
const { username, password, mode } = req.body;
console.log('收到POST认证请求:');
console.log(`username: ${username}`);
console.log(`mode: ${mode || 'verify'}`);
if (password) {
console.log(`password: ${'*'.repeat(password ? password.length : 0)}`); // 为安全起见不打印实际密码
}
const result = handleAuth(username, password, mode);
return res.status(result.status).json(result.response);
});
// 根路径返回服务信息
app.get('/', (req, res) => {
res.send('RDPGW远程认证测试服务已启动');
});
// 启动服务器
app.listen(config.port, () => {
console.log(`认证服务器已启动,监听端口: ${config.port}`);
console.log('当前配置:');
console.log(`- 端口: ${config.port}`);
console.log(`- API路径: ${config.apiPath}`);
console.log(`- 已配置用户数: ${Object.keys(config.users).length}`);
console.log('\n支持的模式:');
console.log(`1. 验证模式 (GET): http://localhost:${config.port}${config.apiPath}?username=testuser&password=testpassword&mode=verify`);
console.log(`2. 密码获取模式 (GET): http://localhost:${config.port}${config.apiPath}?username=testuser&mode=getpassword`);
console.log('---');
console.log('POST请求也支持可以通过请求体发送参数');
});