diff --git a/.env.example b/.env.example index a796b496..04cf2a43 100644 --- a/.env.example +++ b/.env.example @@ -58,4 +58,40 @@ ENABLE_CORS=true TRUST_PROXY=true # 🔒 客户端限制(可选) -# ALLOW_CUSTOM_CLIENTS=false \ No newline at end of file +# 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 \ No newline at end of file diff --git a/config/config.example.js b/config/config.example.js index 87f0218d..414d6e1f 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -131,7 +131,20 @@ const config = { 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'], 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: { username: process.env.LDAP_USER_ATTR_USERNAME || 'uid', diff --git a/src/services/ldapService.js b/src/services/ldapService.js index 4d8238be..98b63593 100644 --- a/src/services/ldapService.js +++ b/src/services/ldapService.js @@ -12,20 +12,76 @@ class LdapService { // 🔗 创建LDAP客户端连接 createClient() { try { - const client = ldap.createClient({ + const clientOptions = { url: this.config.server.url, timeout: this.config.server.timeout, connectTimeout: this.config.server.connectTimeout, 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) => { - 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', () => { - 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', () => { @@ -280,16 +336,30 @@ class LdapService { // 📊 获取LDAP配置信息(不包含敏感信息) getConfigInfo() { - return { + const configInfo = { enabled: this.config.enabled, server: { url: this.config.server.url, searchBase: this.config.server.searchBase, searchFilter: this.config.server.searchFilter, - timeout: this.config.server.timeout + timeout: this.config.server.timeout, + connectTimeout: this.config.server.connectTimeout }, 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 } }