如何使用Node.js结合Azure AD 2.0 Passport策略读取OneDrive文件?
Node.js 通过 Passport Azure AD 2.0 读取 OneDrive 文件完整方案
我来给你一套亲测可行的方案,从认证到读取文件一步到位,帮你避开官方文档里没说清的细节:
一、Azure AD 应用前置配置
首先得在Azure Portal里搞定应用注册,这一步错了后面全白搭:
- 注册一个Web应用(适配Node.js后端场景)
- 设置重定向URI:比如
http://localhost:3000/auth/callback(本地测试用) - 记录下
客户端ID(CLIENT_ID)、客户端密码(CLIENT_SECRET)、租户ID(TENANT_ID) - 前往「API权限」,添加Microsoft Graph的权限:
- 委派权限:
Files.Read(读取当前用户的文件)或Files.Read.All(读取组织内所有文件,需管理员同意) - 别忘了点击「授予管理员同意」(个人账号登录后会自动授权,租户应用需要管理员操作)
- 委派权限:
二、项目初始化与依赖安装
先创建项目并安装必备依赖:
mkdir onedrive-node && cd onedrive-node npm init -y npm install express passport passport-azure-ad axios dotenv express-session
三、Passport Azure AD 认证配置
- 创建
.env文件存储敏感信息:
CLIENT_ID=你的客户端ID CLIENT_SECRET=你的客户端密码 TENANT_ID=你的租户ID REDIRECT_URI=http://localhost:3000/auth/callback SESSION_SECRET=随便写一个字符串(比如abc123xyz) PORT=3000
- 编写核心服务文件
app.js:
require('dotenv').config(); const express = require('express'); const session = require('express-session'); const passport = require('passport'); const { OIDCStrategy } = require('passport-azure-ad'); const axios = require('axios'); const app = express(); // 配置Session,用于存储用户认证状态和Token app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: false } // 本地测试用false,生产环境必须开true(HTTPS环境) })); // 初始化Passport app.use(passport.initialize()); app.use(passport.session()); // 序列化/反序列化用户(将用户信息存入Session) passport.serializeUser((user, done) => done(null, user)); passport.deserializeUser((user, done) => done(null, user)); // 配置Azure AD OIDC认证策略 passport.use(new OIDCStrategy({ identityMetadata: `https://login.microsoftonline.com/${process.env.TENANT_ID}/v2.0/.well-known/openid-configuration`, clientID: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, responseType: 'code id_token', responseMode: 'form_post', redirectUrl: process.env.REDIRECT_URI, allowHttpForRedirectUrl: true, scope: ['openid', 'profile', 'email', 'Files.Read', 'offline_access'], // offline_access用于获取刷新Token loggingLevel: 'info', passReqToCallback: false }, (iss, sub, profile, accessToken, refreshToken, done) => { // 将Token附加到用户对象,方便后续调用Graph API profile.accessToken = accessToken; profile.refreshToken = refreshToken; return done(null, profile); })); // 认证路由:触发Azure AD登录 app.get('/login', passport.authenticate('azuread-openidconnect', { failureRedirect: '/' }), (req, res) => { res.redirect('/'); }); // 回调路由:处理Azure AD认证后的跳转 app.get('/auth/callback', passport.authenticate('azuread-openidconnect', { failureRedirect: '/' }), (req, res) => { res.redirect('/read-onedrive-file'); }); // 退出登录路由 app.get('/logout', (req, res) => { req.logout(() => res.redirect('/')); }); // 核心功能:读取OneDrive文件 app.get('/read-onedrive-file', async (req, res) => { if (!req.isAuthenticated()) { return res.redirect('/login'); } try { // 1. 获取OneDrive根目录下的文件列表 const filesResponse = await axios.get('https://graph.microsoft.com/v1.0/me/drive/root/children', { headers: { Authorization: `Bearer ${req.user.accessToken}` } }); const firstFile = filesResponse.data.value[0]; if (!firstFile) { return res.send('OneDrive根目录下没有文件哦'); } // 2. 获取目标文件内容(小文件用text模式,大文件建议用流式下载) const fileContentResponse = await axios.get(`https://graph.microsoft.com/v1.0/me/drive/items/${firstFile.id}/content`, { headers: { Authorization: `Bearer ${req.user.accessToken}` }, responseType: 'text' // 二进制文件可改为'arraybuffer' }); res.send(` <h3>找到文件:${firstFile.name}</h3> <h4>文件内容:</h4> <pre>${fileContentResponse.data}</pre> `); } catch (error) { // Token过期处理:用Refresh Token获取新的Access Token if (error.response?.status === 401) { const tokenResponse = await axios.post(`https://login.microsoftonline.com/${process.env.TENANT_ID}/oauth2/v2.0/token`, new URLSearchParams({ client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET, grant_type: 'refresh_token', refresh_token: req.user.refreshToken, scope: 'Files.Read offline_access' })); // 更新Session中的Token req.user.accessToken = tokenResponse.data.access_token; req.user.refreshToken = tokenResponse.data.refresh_token; return res.redirect('/read-onedrive-file'); } res.send(`出错了:${error.message}`); } }); // 首页 app.get('/', (req, res) => { if (req.isAuthenticated()) { res.send(` <h1>已登录成功</h1> <a href="/read-onedrive-file">读取OneDrive文件</a> | <a href="/logout">退出登录</a> `); } else { res.send('<a href="/login">点击登录Azure AD</a>'); } }); app.listen(process.env.PORT, () => { console.log(`服务运行在http://localhost:${process.env.PORT}`); });
四、运行测试
- 启动服务:
node app.js - 访问
http://localhost:3000,点击登录并完成微软账号授权 - 授权成功后自动跳转到文件读取页面,即可看到OneDrive中的文件内容
五、简化方案:直接使用微软Graph SDK
如果你觉得Passport配置繁琐,可以用官方Graph SDK,代码更简洁:
npm install @microsoft/microsoft-graph-client @azure/msal-node
示例代码(后台服务模式,无需用户交互):
require('dotenv').config(); const { Client } = require('@microsoft/microsoft-graph-client'); const { ClientCredentialAuthenticationProvider } = require('@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials'); const { ClientSecretCredential } = require('@azure/identity'); // 客户端凭证模式(适合后台服务,需应用级权限) const credential = new ClientSecretCredential( process.env.TENANT_ID, process.env.CLIENT_ID, process.env.CLIENT_SECRET ); const authProvider = new ClientCredentialAuthenticationProvider(credential, { scopes: ['https://graph.microsoft.com/.default'] }); const client = Client.initWithMiddleware({ authProvider }); // 读取指定用户的OneDrive文件(需Files.Read.All权限) async function getOneDriveFiles(userId) { try { const files = await client.api(`/users/${userId}/drive/root/children`).get(); console.log('文件列表:', files.value); const firstFile = files.value[0]; const fileContent = await client.api(`/users/${userId}/drive/items/${firstFile.id}/content`).get(); console.log('文件内容:', fileContent); } catch (error) { console.error('出错了:', error); } } // 替换为目标用户的ID或邮箱 getOneDriveFiles('user@yourdomain.com');
常见坑点提醒
- 必须添加
offline_accessscope,否则无法获取Refresh Token,Access Token过期后会无法继续调用API - 权限类型要匹配:用户交互场景用委派权限,后台服务场景用应用权限
- 大文件建议使用流式下载,避免内存溢出
- 本地测试时,Azure Portal中的重定向URI要和代码中完全一致(包括http/https)
内容的提问来源于stack exchange,提问作者dawood
相关产品推荐
相关产品推荐

