Compare commits

...

7 Commits

Author SHA1 Message Date
Lierfang Support Team
564ee12988 update version
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Go / Build (push) Has been cancelled
2025-06-13 15:57:04 +08:00
14cbe3b9f2 同步动态rdp网关功能
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Go / Build (push) Has been cancelled
2025-05-29 15:27:17 +08:00
fe3f8885c6 增加apiurl字段 2025-04-02 11:23:30 +08:00
760ae5cf8d 增加git忽略 2025-04-02 11:13:15 +08:00
a5be141bac 增加连接信息 2025-04-02 11:08:17 +08:00
7ac73b9489 增加服务端验证
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Go / Build (push) Has been cancelled
2025-04-01 22:12:24 +08:00
f72a4416e3 Add debian support
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker Image CI / build (push) Has been cancelled
Go / Build (push) Has been cancelled
2025-02-14 21:49:34 +08:00
23 changed files with 803 additions and 6 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
go.sum
node_modules/
package-lock.json

View File

@ -26,7 +26,7 @@ ifneq ($(GIT_TAG),)
endif
.PHONY: all
all: mod build
all: mod build deb
# ------------------------------------------------------------------------------
# build
@ -38,6 +38,10 @@ $(BINDIR)/$(BINNAME): $(SRC)
go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/rdpgw
go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME2) ./cmd/auth
.PHONY: deb
deb:clean mod build
dpkg-buildpackage -b -us -uc
# ------------------------------------------------------------------------------
# install
@ -50,7 +54,7 @@ install: build
.PHONY: mod
mod:
go mod tidy -compat=1.22
go mod tidy -compat=1.23
# ------------------------------------------------------------------------------
# test
@ -64,6 +68,7 @@ test:
.PHONY: clean
clean:
@rm -rf '$(BINDIR)' ./_dist
dh_clean
.PHONY: info
info:

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,19 @@ func main() {
if err != nil {
log.Fatal(err)
}
server := grpc.NewServer()
db := database.NewConfig(conf.Users)
// 根据配置选择使用API认证或本地配置认证
var db database.Database
if conf.PXVDI.Enabled && conf.PXVDI.ApiUrl != "" {
log.Printf("Using API authentication, API URL: %s", conf.PXVDI.ApiUrl)
db = database.NewApiDb(conf.PXVDI.ApiUrl, conf.PXVDI.ApiKey)
} else {
log.Printf("Using local configuration file authentication")
db = database.NewConfig(conf.Users)
}
service := NewAuthService(opts.ServiceName, db)
auth.RegisterAuthenticateServer(server, service)
server.Serve(listener)

View File

@ -11,6 +11,13 @@ import (
type Configuration struct {
Users []UserConfig `koanf:"users"`
PXVDI PXVDIConfig `koanf:"pxvdi"`
}
type PXVDIConfig struct {
Enabled bool `koanf:"enabled"`
ApiUrl string `koanf:"apiurl"`
ApiKey string `koanf:"apikey"`
}
type UserConfig struct {
@ -36,6 +43,7 @@ func Load(configFile string) Configuration {
koanfTag := koanf.UnmarshalConf{Tag: "koanf"}
k.UnmarshalWithConf("Users", &Conf.Users, koanfTag)
k.UnmarshalWithConf("PXVDI", &Conf.PXVDI, koanfTag)
return Conf

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

@ -0,0 +1,181 @@
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}作为占位符
ApiKey string // API密钥
}
// NewApiDb 创建一个新的ApiDb实例
func NewApiDb(apiUrl string, apiKey string) *ApiDb {
return &ApiDb{
ApiUrl: apiUrl,
ApiKey: apiKey,
}
}
// 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
fullUrl := fmt.Sprintf("%s/api/custom/public/ntlmcheck", a.ApiUrl)
// 构建POST请求体
requestData := map[string]string{
"user": username,
"apikey": a.ApiKey,
}
jsonData, err := json.Marshal(requestData)
if err != nil {
log.Printf("Failed to marshal request data: %v", err)
return ""
}
log.Printf("Sending API password POST request to: %s", fullUrl)
// 创建自定义HTTP客户端跳过SSL证书验证
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
// 发送POST请求到API使用不验证SSL证书的客户端
resp, err := client.Post(fullUrl, "application/json", bytes.NewBuffer(jsonData))
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 {
Success bool `json:"success"`
Data struct {
Pass string `json:"pass"`
} `json:"data"`
}
err = json.NewDecoder(bytes.NewReader(body)).Decode(&result)
if err != nil {
log.Printf("Failed to parse API response: %v", err)
return ""
}
// 检查响应内容
if !result.Success || result.Data.Pass == "" {
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.Data.Pass
}
// 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: Trying to validate user: %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: Successfully retrieved password for user: %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: User %s authenticated successfully", username)
return nil
}

View File

@ -34,6 +34,13 @@ type Configuration struct {
Caps RDGCapsConfig `koanf:"caps"`
Security SecurityConfig `koanf:"security"`
Client ClientConfig `koanf:"client"`
PXVDI PXVDIConfig `koanf:"pxvdi"`
}
type PXVDIConfig struct {
Enabled bool `koanf:"enabled"`
ApiUrl string `koanf:"apiurl"`
ApiKey string `koanf:"apikey"`
}
type ServerConfig struct {
@ -184,6 +191,7 @@ func Load(configFile string) Configuration {
k.UnmarshalWithConf("Security", &Conf.Security, koanfTag)
k.UnmarshalWithConf("Client", &Conf.Client, koanfTag)
k.UnmarshalWithConf("Kerberos", &Conf.Kerberos, koanfTag)
k.UnmarshalWithConf("PXVDI", &Conf.PXVDI, koanfTag)
if len(Conf.Security.PAATokenEncryptionKey) != 32 {
Conf.Security.PAATokenEncryptionKey, _ = security.GenerateRandomString(32)

View File

@ -3,6 +3,7 @@ package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/bolkedebruin/gokrb5/v8/keytab"
"github.com/bolkedebruin/gokrb5/v8/service"
@ -23,6 +24,7 @@ import (
"net/url"
"os"
"strconv"
"time"
)
const (
@ -65,6 +67,27 @@ func initOIDC(callbackUrl *url.URL) *web.OIDC {
return o.New()
}
// 定时记录连接用户信息
func startConnectionLogger(interval time.Duration) {
ticker := time.NewTicker(interval)
go func() {
for range ticker.C {
connections := protocol.GetActiveConnections()
if len(connections) > 0 {
connData, err := json.Marshal(connections)
if err != nil {
log.Printf("连接信息序列化失败: %v", err)
continue
}
log.Printf("当前活跃连接数: %d, 连接详情: %s", len(connections), string(connData))
} else {
log.Printf("当前无活跃连接")
}
}
}()
log.Printf("启动连接信息记录器,间隔时间: %v", interval)
}
func main() {
// load config
_, err := flags.Parse(&opts)
@ -125,6 +148,10 @@ func main() {
h := w.NewHandler()
log.Printf("Starting remote desktop gateway server")
// 启动连接信息记录器
startConnectionLogger(10 * time.Second)
cfg := &tls.Config{}
// configure tls security

View File

@ -1,6 +1,9 @@
package protocol
import "fmt"
import (
"fmt"
"time"
)
var Connections map[string]*Monitor
@ -41,6 +44,40 @@ func Disconnect(id string) error {
return fmt.Errorf("%s connection does not exist", id)
}
// GetActiveConnections 返回所有当前活跃的连接信息
func GetActiveConnections() []map[string]interface{} {
connections := []map[string]interface{}{}
for id, monitor := range Connections {
tunnel := monitor.Tunnel
if tunnel == nil {
continue
}
// 计算连接持续时间
duration := time.Since(tunnel.ConnectedOn)
// 收集每个连接的关键信息
connInfo := map[string]interface{}{
"id": id,
"rdgId": tunnel.RDGId,
"targetServer": tunnel.TargetServer,
"remoteAddr": tunnel.RemoteAddr,
"userName": tunnel.User.UserName(),
"domain": tunnel.User.Domain(),
"connectedOn": tunnel.ConnectedOn,
"lastSeen": tunnel.LastSeen,
"bytesSent": tunnel.BytesSent,
"bytesReceived": tunnel.BytesReceived,
"durationSecs": int(duration.Seconds()),
}
connections = append(connections, connInfo)
}
return connections
}
// CalculateSpeedPerSecond calculate moving average.
/*
func CalculateSpeedPerSecond(connId string) (in int, out int) {

11
debian/changelog vendored Normal file
View File

@ -0,0 +1,11 @@
rdpgw (2.0.3) UNRELEASED; urgency=medium
* add Dynamic Gateway
-- Lierfang Support Team <itsupport@lierfang.com> Fri, 13 Jun 2025 15:56:06 +0800
rdpgw (2.0.2) UNSTABLE; urgency=medium
* init
-- Jiangcuo <Jiangcuo@lierfang.com> Mon, 03 Feb 2025 13:56:49 +0800

21
debian/control vendored Normal file
View File

@ -0,0 +1,21 @@
Source: rdpgw
Section: admin
Priority: optional
Maintainer: Lierfang <it_support@lierfang.com>
Homepage: https://github.com/bolkedebruin/rdpgw
Build-Depends: debhelper-compat (= 12),
golang ( >= 1.23.5-1~bpo12+1 ),
build-essential,
libpam0g-dev,
dh-golang
Package: rdpgw
Architecture: any
Depends: dbus,
openssl,
${misc:Depends},
${shlibs:Depends},
Description: rdpgw
RDPGW is an implementation of the Remote Desktop Gateway protocol.
This allows you to connect with the official Microsoft clients to remote desktops over HTTPS.
These desktops could be, for example, XRDP desktops running in containers on Kubernetes.

11
debian/copyright vendored Normal file
View File

@ -0,0 +1,11 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: https://github.com/bolkedebruin/rdpgw
Files: *
Copyright: bolkedebruin
License: Apache-2.0
Files: debian/*
Copyright: Lierfang <service@lierfang.com>
License: Apache-2.0

5
debian/install vendored Normal file
View File

@ -0,0 +1,5 @@
bin/rdpgw usr/bin/
bin/rdpgw-auth usr/sbin/
debian/rdpgw.yaml etc/rdpgw/
debian/rdpgw-auth.yaml etc/rdpgw/
debian/rdpgw-auth.service lib/systemd/system/

21
debian/postinst vendored Normal file
View File

@ -0,0 +1,21 @@
#!/bin/sh
set -e
#DEBHELPER#
case "$1" in
configure)
if [ ! -f "/etc/rdpgw/server.pem" ]; then
random=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
openssl genrsa -des3 -passout pass:$random -out /tmp/server.pass.key 2048
openssl rsa -passin pass:$random -in /tmp/server.pass.key -out /etc/rdpgw/key.pem
rm /tmp/server.pass.key
openssl req -new -sha256 -key /etc/rdpgw/key.pem -out /etc/rdpgw/server.csr -subj "/C=US/ST=VA/L=SomeCity/O=MyCompany/OU=MyDivision/CN=rdpgw"
openssl x509 -req -days 365 -in /etc/rdpgw/server.csr -signkey /etc/rdpgw/key.pem -out /etc/rdpgw/server.pem
fi
deb-systemd-invoke reload-or-try-restart rdpgw-auth.service || true
deb-systemd-invoke reload-or-try-restart rdpgw.service || true
;;
esac

16
debian/rdpgw-auth.service vendored Normal file
View File

@ -0,0 +1,16 @@
[Unit]
Description=RDP Gateway Auth Service
After=network.target
StartLimitBurst=5
StartLimitInterval=10s
[Service]
Type=simple
User=root
ExecStart=/usr/sbin/rdpgw-auth -c /etc/rdpgw/rdpgw-auth.yaml -s /run/rdpgw-auth.sock
Restart=on-failure
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

6
debian/rdpgw-auth.yaml vendored Normal file
View File

@ -0,0 +1,6 @@
PXVDI:
Enabled: true
apiUrl: "https://10.13.16.164:3002"
apiKey: "dasdasdasdas"
Users:
- {Username: "debian-rdpgw-start", Password: "debian-rdpgw-password"}

16
debian/rdpgw.service vendored Normal file
View File

@ -0,0 +1,16 @@
[Unit]
Description=RDP Gateway Service
After=network.target
StartLimitBurst=5
StartLimitInterval=10s
[Service]
Type=simple
User=root
ExecStart=/usr/bin/rdpgw -c /etc/rdpgw/rdpgw.yaml
Restart=on-failure
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

30
debian/rdpgw.yaml vendored Normal file
View File

@ -0,0 +1,30 @@
Server:
Authentication:
- ntlm
BasicAuthTimeout: 5
AuthSocket: /run/rdpgw-auth.sock
GatewayAddress: localhost
Port: 443
Hosts:
- localhost:3389
HostSelection: any
Tls: enable
CertFile: /etc/rdpgw/server.pem
KeyFile: /etc/rdpgw/key.pem
Caps:
SmartCardAuth: false
TokenAuth: false
IdleTimeout: 10
EnablePrinter: true
EnablePort: true
EnablePnp: true
EnableDrive: true
EnableClipboard: true
Client:
UsernameTemplate: "{{ username }}"
SplitUserDomain: false
Security:
PAATokenSigningKey: thisisasessionkeyreplacethisjetzt
UserTokenEncryptionKey: thisisasessionkeyreplacethisjetzt
EnableUserToken: false
VerifyClientIp: trufalsee

19
debian/rules vendored Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
include /usr/share/dpkg/pkg-info.mk
%:
dh $@
override_dh_auto_build:
override_dh_auto_test:
override_dh_auto_install:
override_dh_auto_clean:
override_dh_dwz:

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请求也支持可以通过请求体发送参数');
});