Azure LDAP认证在云服务器及其他设备上连接被拒求助
问题:Azure LDAP认证在本地正常,部署到DigitalOcean后连接被拒
我是Azure LDAP新手,正在开发Next.js站点,用户可通过Microsoft Entra ID(Azure Active Directory)的邮箱密码完成认证。该功能在本地http://localhost:3000可正常登录,但部署到DigitalOcean云主机后出现连接被拒错误,在其他笔记本使用时也有同样问题。请问我是否遗漏了配置,或是需要为云主机生成证书?
以下是用于连接LDAP的代码:
import { NextResponse } from "next/server"; import ldap from "ldapjs"; import fs from "fs"; import path from "path"; import mysql from "mysql2/promise"; import jwt from "jsonwebtoken"; export async function POST(req) { if (req.method === 'POST') { const { username, email, password, token = '0' } = await req.json(); var db = null; let ldapSettings = null; try { db = await mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, }) const [ldapSettingsData] = await db.execute(`SELECT * from ldap_settings WHERE login_token='${token}'`); if (ldapSettingsData.length == 0) { return NextResponse.json({ success: false, "error": "LDAP Settings not found!" }, { status: 400 }) } if (!ldapSettingsData[0]['ldap_enabled']) { return NextResponse.json({ success: false, "error": "LDAP not available, Contact admin for LDAP Login!" }, { status: 400 }) } ldapSettings = ldapSettingsData; } catch (e) { return NextResponse.json({ success: false, "error": "Server error occurred!" }, { status: 500 }) } finally { if (db) { db.destroy(); } } let ldapCert = null; try { ldapCert = path.join(process.cwd(), 'public', "certificates", (ldapSettings?.[0]?.["ldap_cert_file"] ?? "")); ldapCert = fs.readFileSync(ldapCert); } catch (e) { return NextResponse.json({ error: "Certificate File not found!" }, { status: 404 }); } const client = ldap.createClient({ url: ldapSettings[0]['ldap_server'] + ":" + (ldapSettings[0]['use_tls'] ? "389" : "636"), tlsOptions: { ca: [ldapCert], rejectUnauthorized: (ldapSettings?.[0]?.ldap_ssl_cert_verif != null && ldapSettings[0]['ldap_ssl_cert_verif'] ? false : true) }, reconnect: true, }); client.on('error', (err) => { console.error("LDAP connection error:", err.message); if (err.code) { console.error("Error code:", err.code); } }); client.on('connect', () => { console.log("Successfully connected to the LDAP server."); }); return new Promise((resolve) => { let response = null; client.bind(("CN=" + username + `,${ldapSettings[0]['is_azure_ad'] ? ldapSettings[0]['base_bind_dn'].replace(/ou=|OU=/g, "ou=AADDC ") : ldapSettings[0]['base_bind_dn']}`), password, (error) => { if (error) { response = NextResponse.json({ success: false, error: "Invalid LDAP Credentials or LDAP Error Occured!" }, { status: 401 }); client.unbind(); resolve(response); return; } else { const opts = { filter: ldapSettings[0]['ldap_filter'].replace(/{email}/g, email).replace(/{username}/g, username), scope: 'sub', attributes: [], } let user_exists = false; client.search("CN=" + username + `,${ldapSettings[0]['is_azure_ad'] ? ldapSettings[0]['base_bind_dn'].replace(/ou=|OU=/g, "ou=AADDC ") : ldapSettings[0]['base_bind_dn']}`, opts, (err, res) => { if (err) { response = NextResponse.json({ success: false, error: "Search Query Error" }, { status: 401 }); client.unbind(); resolve(response); return; } res.on('searchEntry', (entry) => { user_exists = true; }); res.on('error', (err) => { console.log(err); response = NextResponse.json({ success: false, error: "User finding error!" }, { status: 401 }); client.unbind(); resolve(response); }); res.on('end', async (result) => { if (user_exists) { let db = null; try { db = await mysql.createConnection({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, port: process.env.DB_PORT }); const [rows] = await db.execute('SELECT id, source, admin, name from users WHERE email=? and source=?', [email, "ldap"]); let user_id = ''; let adminId = ldapSettings[0]['admin_id']; let user_dp = ''; let user_name = ''; if (rows.length === 0) { await db.execute("INSERT INTO users SET admin=?, name=?, email=?, source=?, role=?", [adminId, username, email, 'ldap', 'employee']).then(([result]) => { user_id = result.insertId; }); user_name = username; } else { user_id = rows[0]['id']; adminId = rows[0]['admin']; user_dp = (rows[0]?.dp ?? ""); user_name = (rows[0]?.name ?? ""); } const SECRET_KEY = process.env.JWT_SECRET || 'your_secret_key'; const token = jwt.sign( { id: user_id, admin_id: adminId, name: user_name, email: email, source: "LDAP", dp: (user_dp ?? ''), role: 'employee'}, SECRET_KEY, { expiresIn: process.env.JWT_TOKEN_EXPIRES || '24h' } ); const response = NextResponse.json({ message: 'Authenticated successfully!', }, { status: 200 }); response.cookies.set('token', token, { httpOnly: true, maxAge: process.env.COOKIES_EXPIRES_SECONDS || (24 * 60 * 60), path: '/', }); client.unbind(); resolve(response); } catch (error) { console.error('Error logging in:', error); response = NextResponse.json({ error: 'Error Occured within Server!' }, { status: 500 }); client.unbind(); resolve(response); } finally { if (db) { db.destroy(); } } } else { response = NextResponse.json({ success: false, error: "User not found!" }, { status: 401 }); } client.unbind(); resolve(response); }); }); } }); }); } else { return NextResponse.json({ error: 'Only POST requests are allowed' }, { status: 405 }); } }
可能的原因及解决步骤
1. 网络/防火墙限制
- DigitalOcean云主机的Cloud Firewall需要放行对应LDAP端口:
- LDAPS(SSL)模式放行636端口(TCP)
- STARTTLS模式放行389端口(TCP)
- 检查Azure端配置:将DigitalOcean主机的公网IP添加到Azure AD Domain Services(AAD DS)的允许访问列表,Azure默认会限制非信任IP的LDAP连接。
2. 证书配置问题
- 确认DigitalOcean主机上的证书文件存在于
public/certificates/目录,且Node.js进程拥有读取权限。 - 无需为云主机生成证书,但必须使用Azure官方提供的根CA证书(可从Azure门户下载AAD DS根证书),替换当前使用的证书文件。
- 生产环境建议将
rejectUnauthorized设置为true,避免跳过证书验证带来的安全风险,确保ldap_ssl_cert_verif配置项正确。
3. LDAP连接参数验证
- 确认
ldap_server配置的是Azure LDAP服务的正确域名(如yourdomain.aadds.azure.com),而非本地测试地址。 - 端口逻辑检查:代码中
use_tls=true对应389端口(STARTTLS),use_tls=false对应636端口(LDAPS),需与Azure AAD DS的默认配置一致。 - 绑定DN格式验证:Azure AD的绑定DN格式应为
CN=用户名,OU=AADDC Users,DC=yourdomain,DC=aadds,DC=azure,DC=com,确保代码中base_bind_dn的替换逻辑无格式错误。
4. 代码逻辑优化
- 若启用
use_tls,需取消starttls相关代码的注释,确保在绑定前建立加密连接:client.starttls({ ca: [ldapCert] }, (err) => { if (err) { console.error('STARTTLS失败:', err); client.unbind(); resolve(NextResponse.json({ success: false, error: "STARTTLS连接失败!" }, { status: 400 })); return; } // 绑定操作放在starttls成功后执行 client.bind(...) }); - 完善错误处理:在
client.on('error')中主动resolve错误响应,避免请求挂起。
内容的提问来源于stack exchange,提问作者Ovais Jetnetix
相关产品推荐
相关产品推荐

