MSAL-node部署Windows服务器遇500错误:如何查看MSAL日志?
问题描述
我按照微软的MSAL-node Express教程实现了认证功能,本地运行完全正常,但部署到远程Windows服务器后返回500错误,且服务器日志中找不到相关记录。
核心问题:MSAL自带日志功能,请问该在哪里查看它的日志?
Express路由代码:
router.get("/login", (req: Request, res: Response, next: NextFunction) => { // ... 其他逻辑 aadMsalAuthenticator.login(aadRequest)(req, res, next); });
教程中的MSAL认证器代码:
const msal = require('@azure/msal-node'); const axios = require('axios'); const { msalConfig } = require('../authConfig'); class AuthProvider { msalConfig; cryptoProvider; constructor(msalConfig) { this.msalConfig = msalConfig this.cryptoProvider = new msal.CryptoProvider(); }; login(options = {}) { return async (req, res, next) => { const state = this.cryptoProvider.base64Encode( JSON.stringify({ successRedirect: options.successRedirect || '/', }) ); const authCodeUrlRequestParams = { state: state, scopes: options.scopes || [], redirectUri: options.redirectUri, }; const authCodeRequestParams = { state: state, scopes: options.scopes || [], redirectUri: options.redirectUri, }; if (!this.msalConfig.auth.cloudDiscoveryMetadata || !this.msalConfig.auth.authorityMetadata) { const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([ this.getCloudDiscoveryMetadata(this.msalConfig.auth.authority), this.getAuthorityMetadata(this.msalConfig.auth.authority) ]); this.msalConfig.auth.cloudDiscoveryMetadata = JSON.stringify(cloudDiscoveryMetadata); this.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata); } const msalInstance = this.getMsalInstance(this.msalConfig); return this.redirectToAuthCodeUrl( authCodeUrlRequestParams, authCodeRequestParams, msalInstance )(req, res, next); }; } acquireToken(options = {}) { return async (req, res, next) => { try { const msalInstance = this.getMsalInstance(this.msalConfig); if (req.session.tokenCache) { msalInstance.getTokenCache().deserialize(req.session.tokenCache); } const tokenResponse = await msalInstance.acquireTokenSilent({ account: req.session.account, scopes: options.scopes || [], }); req.session.tokenCache = msalInstance.getTokenCache().serialize(); req.session.accessToken = tokenResponse.accessToken; req.session.idToken = tokenResponse.idToken; req.session.account = tokenResponse.account; res.redirect(options.successRedirect); } catch (error) { if (error instanceof msal.InteractionRequiredAuthError) { return this.login({ scopes: options.scopes || [], redirectUri: options.redirectUri, successRedirect: options.successRedirect || '/', })(req, res, next); } next(error); } }; } handleRedirect(options = {}) { return async (req, res, next) => { if (!req.body || !req.body.state) { return next(new Error('Error: response not found')); } const authCodeRequest = { ...req.session.authCodeRequest, code: req.body.code, codeVerifier: req.session.pkceCodes.verifier, }; try { const msalInstance = this.getMsalInstance(this.msalConfig); if (req.session.tokenCache) { msalInstance.getTokenCache().deserialize(req.session.tokenCache); } const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body); req.session.tokenCache = msalInstance.getTokenCache().serialize(); req.session.idToken = tokenResponse.idToken; req.session.account = tokenResponse.account; req.session.isAuthenticated = true; const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state)); res.redirect(state.successRedirect); } catch (error) { next(error); } } } logout(options = {}) { return (req, res, next) => { let logoutUri = `${this.msalConfig.auth.authority}/oauth2/v2.0/`; if (options.postLogoutRedirectUri) { logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`; } req.session.destroy(() => { res.redirect(logoutUri); }); } } getMsalInstance(msalConfig) { return new msal.ConfidentialClientApplication(msalConfig); } redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) { return async (req, res, next) => { const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes(); req.session.pkceCodes = { challengeMethod: 'S256', verifier: verifier, challenge: challenge, }; req.session.authCodeUrlRequest = { ...authCodeUrlRequestParams, responseMode: msal.ResponseMode.FORM_POST, codeChallenge: req.session.pkceCodes.challenge, codeChallengeMethod: req.session.pkceCodes.challengeMethod, }; req.session.authCodeRequest = { ...authCodeRequestParams, code: '', }; try { const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest); res.redirect(authCodeUrlResponse); } catch (error) { next(error); } }; } async getCloudDiscoveryMetadata(authority) { const endpoint = 'https://login.microsoftonline.com/common/discovery/instance'; try { const response = await axios.get(endpoint, { params: { 'api-version': '1.1', 'authorization_endpoint': `${authority}/oauth2/v2.0/authorize` } }); return await response.data; } catch (error) { throw error; } } async getAuthorityMetadata(authority) { const endpoint = `${authority}/v2.0/.well-known/openid-configuration`; try { const response = await axios.get(endpoint); return await response.data; } catch (error) { console.log(error); } } } const authProvider = new AuthProvider(msalConfig); module.exports = authProvider;
解决方案
要启用MSAL-node的日志功能,需要在初始化ConfidentialClientApplication时添加日志配置,具体步骤如下:
1. 修改MSAL配置
在定义msalConfig的文件(比如authConfig.js)中,添加system字段配置日志:
const msal = require('@azure/msal-node'); const msalConfig = { auth: { clientId: "你的客户端ID", authority: "你的授权地址", clientSecret: "你的客户端密钥" }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { // 输出到控制台 console.log(message); // 也可以写入文件(需确保Node进程有写入权限) // const fs = require('fs'); // fs.appendFileSync('C:\\inetpub\\wwwroot\\your-app\\msal-logs.txt', message + '\n'); }, piiLoggingEnabled: false, // 禁止记录个人身份信息,符合隐私要求 logLevel: msal.LogLevel.Verbose // 日志级别:Error/Warning/Info/Verbose } } }; module.exports = { msalConfig };
2. 查看日志的方式
- 控制台日志:如果直接用命令行启动Node应用,日志会输出到命令行窗口;如果使用PM2、IIS等进程管理器,需查看对应工具的日志捕获路径。
- 文件日志:按照上述配置写入文件后,直接打开指定路径的日志文件即可查看。
3. 排查500错误的额外建议
- 检查远程服务器网络是否能访问Azure AD的相关端点(如
login.microsoftonline.com)。 - 确认部署后的
redirectUri与Azure门户中注册的完全一致(包括域名、端口、路径)。 - 给Express添加全局错误处理中间件,捕获并记录详细错误信息:
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('服务器内部错误'); });
内容的提问来源于stack exchange,提问作者Anonymous
相关产品推荐
相关产品推荐

