mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
chore: support LDAPS
This commit is contained in:
38
.env.example
38
.env.example
@@ -58,4 +58,40 @@ ENABLE_CORS=true
|
|||||||
TRUST_PROXY=true
|
TRUST_PROXY=true
|
||||||
|
|
||||||
# 🔒 客户端限制(可选)
|
# 🔒 客户端限制(可选)
|
||||||
# ALLOW_CUSTOM_CLIENTS=false
|
# ALLOW_CUSTOM_CLIENTS=false
|
||||||
|
|
||||||
|
# 🔐 LDAP 认证配置
|
||||||
|
LDAP_ENABLED=false
|
||||||
|
LDAP_URL=ldaps://ldap-1.test1.bj.yxops.net:636
|
||||||
|
LDAP_BIND_DN=cn=admin,dc=example,dc=com
|
||||||
|
LDAP_BIND_PASSWORD=admin_password
|
||||||
|
LDAP_SEARCH_BASE=dc=example,dc=com
|
||||||
|
LDAP_SEARCH_FILTER=(uid={{username}})
|
||||||
|
LDAP_SEARCH_ATTRIBUTES=dn,uid,cn,mail,givenName,sn
|
||||||
|
LDAP_TIMEOUT=5000
|
||||||
|
LDAP_CONNECT_TIMEOUT=10000
|
||||||
|
|
||||||
|
# 🔒 LDAP TLS/SSL 配置 (用于 ldaps:// URL)
|
||||||
|
# 是否忽略证书验证错误 (设置为false可忽略自签名证书错误)
|
||||||
|
LDAP_TLS_REJECT_UNAUTHORIZED=true
|
||||||
|
# CA 证书文件路径 (可选,用于自定义CA证书)
|
||||||
|
# LDAP_TLS_CA_FILE=/path/to/ca-cert.pem
|
||||||
|
# 客户端证书文件路径 (可选,用于双向认证)
|
||||||
|
# LDAP_TLS_CERT_FILE=/path/to/client-cert.pem
|
||||||
|
# 客户端私钥文件路径 (可选,用于双向认证)
|
||||||
|
# LDAP_TLS_KEY_FILE=/path/to/client-key.pem
|
||||||
|
# 服务器名称 (可选,用于 SNI)
|
||||||
|
# LDAP_TLS_SERVERNAME=ldap.example.com
|
||||||
|
|
||||||
|
# 🗺️ LDAP 用户属性映射
|
||||||
|
LDAP_USER_ATTR_USERNAME=uid
|
||||||
|
LDAP_USER_ATTR_DISPLAY_NAME=cn
|
||||||
|
LDAP_USER_ATTR_EMAIL=mail
|
||||||
|
LDAP_USER_ATTR_FIRST_NAME=givenName
|
||||||
|
LDAP_USER_ATTR_LAST_NAME=sn
|
||||||
|
|
||||||
|
# 👥 用户管理配置
|
||||||
|
USER_MANAGEMENT_ENABLED=false
|
||||||
|
DEFAULT_USER_ROLE=user
|
||||||
|
USER_SESSION_TIMEOUT=86400000
|
||||||
|
MAX_API_KEYS_PER_USER=5
|
||||||
@@ -131,7 +131,20 @@ const config = {
|
|||||||
searchFilter: process.env.LDAP_SEARCH_FILTER || '(uid={{username}})',
|
searchFilter: process.env.LDAP_SEARCH_FILTER || '(uid={{username}})',
|
||||||
searchAttributes: process.env.LDAP_SEARCH_ATTRIBUTES ? process.env.LDAP_SEARCH_ATTRIBUTES.split(',') : ['dn', 'uid', 'cn', 'mail', 'givenName', 'sn'],
|
searchAttributes: process.env.LDAP_SEARCH_ATTRIBUTES ? process.env.LDAP_SEARCH_ATTRIBUTES.split(',') : ['dn', 'uid', 'cn', 'mail', 'givenName', 'sn'],
|
||||||
timeout: parseInt(process.env.LDAP_TIMEOUT) || 5000,
|
timeout: parseInt(process.env.LDAP_TIMEOUT) || 5000,
|
||||||
connectTimeout: parseInt(process.env.LDAP_CONNECT_TIMEOUT) || 10000
|
connectTimeout: parseInt(process.env.LDAP_CONNECT_TIMEOUT) || 10000,
|
||||||
|
// TLS/SSL 配置
|
||||||
|
tls: {
|
||||||
|
// 是否忽略证书错误 (用于自签名证书)
|
||||||
|
rejectUnauthorized: process.env.LDAP_TLS_REJECT_UNAUTHORIZED !== 'false', // 默认验证证书,设置为false则忽略
|
||||||
|
// CA证书文件路径 (可选,用于自定义CA证书)
|
||||||
|
ca: process.env.LDAP_TLS_CA_FILE ? require('fs').readFileSync(process.env.LDAP_TLS_CA_FILE) : undefined,
|
||||||
|
// 客户端证书文件路径 (可选,用于双向认证)
|
||||||
|
cert: process.env.LDAP_TLS_CERT_FILE ? require('fs').readFileSync(process.env.LDAP_TLS_CERT_FILE) : undefined,
|
||||||
|
// 客户端私钥文件路径 (可选,用于双向认证)
|
||||||
|
key: process.env.LDAP_TLS_KEY_FILE ? require('fs').readFileSync(process.env.LDAP_TLS_KEY_FILE) : undefined,
|
||||||
|
// 服务器名称 (用于SNI,可选)
|
||||||
|
servername: process.env.LDAP_TLS_SERVERNAME || undefined
|
||||||
|
}
|
||||||
},
|
},
|
||||||
userMapping: {
|
userMapping: {
|
||||||
username: process.env.LDAP_USER_ATTR_USERNAME || 'uid',
|
username: process.env.LDAP_USER_ATTR_USERNAME || 'uid',
|
||||||
|
|||||||
@@ -12,20 +12,76 @@ class LdapService {
|
|||||||
// 🔗 创建LDAP客户端连接
|
// 🔗 创建LDAP客户端连接
|
||||||
createClient() {
|
createClient() {
|
||||||
try {
|
try {
|
||||||
const client = ldap.createClient({
|
const clientOptions = {
|
||||||
url: this.config.server.url,
|
url: this.config.server.url,
|
||||||
timeout: this.config.server.timeout,
|
timeout: this.config.server.timeout,
|
||||||
connectTimeout: this.config.server.connectTimeout,
|
connectTimeout: this.config.server.connectTimeout,
|
||||||
reconnect: true
|
reconnect: true
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 如果使用 LDAPS (SSL/TLS),添加 TLS 选项
|
||||||
|
if (this.config.server.url.toLowerCase().startsWith('ldaps://')) {
|
||||||
|
const tlsOptions = {}
|
||||||
|
|
||||||
|
// 证书验证设置
|
||||||
|
if (this.config.server.tls) {
|
||||||
|
if (typeof this.config.server.tls.rejectUnauthorized === 'boolean') {
|
||||||
|
tlsOptions.rejectUnauthorized = this.config.server.tls.rejectUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
// CA 证书
|
||||||
|
if (this.config.server.tls.ca) {
|
||||||
|
tlsOptions.ca = this.config.server.tls.ca
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端证书和私钥 (双向认证)
|
||||||
|
if (this.config.server.tls.cert) {
|
||||||
|
tlsOptions.cert = this.config.server.tls.cert
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.server.tls.key) {
|
||||||
|
tlsOptions.key = this.config.server.tls.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器名称 (SNI)
|
||||||
|
if (this.config.server.tls.servername) {
|
||||||
|
tlsOptions.servername = this.config.server.tls.servername
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOptions.tlsOptions = tlsOptions
|
||||||
|
|
||||||
|
logger.debug('🔒 Creating LDAPS client with TLS options:', {
|
||||||
|
url: this.config.server.url,
|
||||||
|
rejectUnauthorized: tlsOptions.rejectUnauthorized,
|
||||||
|
hasCA: !!tlsOptions.ca,
|
||||||
|
hasCert: !!tlsOptions.cert,
|
||||||
|
hasKey: !!tlsOptions.key,
|
||||||
|
servername: tlsOptions.servername
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = ldap.createClient(clientOptions)
|
||||||
|
|
||||||
// 设置错误处理
|
// 设置错误处理
|
||||||
client.on('error', (err) => {
|
client.on('error', (err) => {
|
||||||
logger.error('🔌 LDAP client error:', err)
|
if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
||||||
|
logger.error('🔒 LDAP TLS certificate error:', {
|
||||||
|
code: err.code,
|
||||||
|
message: err.message,
|
||||||
|
hint: 'Consider setting LDAP_TLS_REJECT_UNAUTHORIZED=false for self-signed certificates'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
logger.error('🔌 LDAP client error:', err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('connect', () => {
|
client.on('connect', () => {
|
||||||
logger.info('🔗 LDAP client connected successfully')
|
if (this.config.server.url.toLowerCase().startsWith('ldaps://')) {
|
||||||
|
logger.info('🔒 LDAPS client connected successfully')
|
||||||
|
} else {
|
||||||
|
logger.info('🔗 LDAP client connected successfully')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on('connectTimeout', () => {
|
client.on('connectTimeout', () => {
|
||||||
@@ -280,16 +336,30 @@ class LdapService {
|
|||||||
|
|
||||||
// 📊 获取LDAP配置信息(不包含敏感信息)
|
// 📊 获取LDAP配置信息(不包含敏感信息)
|
||||||
getConfigInfo() {
|
getConfigInfo() {
|
||||||
return {
|
const configInfo = {
|
||||||
enabled: this.config.enabled,
|
enabled: this.config.enabled,
|
||||||
server: {
|
server: {
|
||||||
url: this.config.server.url,
|
url: this.config.server.url,
|
||||||
searchBase: this.config.server.searchBase,
|
searchBase: this.config.server.searchBase,
|
||||||
searchFilter: this.config.server.searchFilter,
|
searchFilter: this.config.server.searchFilter,
|
||||||
timeout: this.config.server.timeout
|
timeout: this.config.server.timeout,
|
||||||
|
connectTimeout: this.config.server.connectTimeout
|
||||||
},
|
},
|
||||||
userMapping: this.config.userMapping
|
userMapping: this.config.userMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 TLS 配置信息(不包含敏感数据)
|
||||||
|
if (this.config.server.url.toLowerCase().startsWith('ldaps://') && this.config.server.tls) {
|
||||||
|
configInfo.server.tls = {
|
||||||
|
rejectUnauthorized: this.config.server.tls.rejectUnauthorized,
|
||||||
|
hasCA: !!this.config.server.tls.ca,
|
||||||
|
hasCert: !!this.config.server.tls.cert,
|
||||||
|
hasKey: !!this.config.server.tls.key,
|
||||||
|
servername: this.config.server.tls.servername
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user