增加服务端验证
This commit is contained in:
parent
f72a4416e3
commit
7ac73b9489
119
README.md
119
README.md
@ -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 访问限制和请求频率限制
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
169
cmd/auth/database/apidb.go
Normal 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
|
||||
}
|
||||
@ -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
84
docs/api-auth.md
Normal 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限制或类似措施
|
||||
23
docs/rdpgw-auth-api.yaml.example
Normal file
23
docs/rdpgw-auth-api.yaml.example
Normal 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
132
index.js
Normal 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请求也支持,可以通过请求体发送参数');
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user