node-ews返回401未授权,使用有效的访问令牌

oxf4rvwz  于 2022-12-29  发布在  Node.js
关注(0)|答案(5)|浏览(283)

我正在使用node-ews从Microsoft Exchange服务器获取电子邮件。它在基本的身份验证中运行良好。
但是,由于微软禁用了基本认证,我们目前正在使用图形资源管理器中的OAuth令牌(访问令牌)进行测试。
但它返回401未授权错误。
这是我们用于连接到exchange服务器的示例代码。

const ewsConfig = {
            username: item.mail_username,
            password: item.user_pass,
            host: item.ews_host,
            token: 'xxxxxxxxxxx',
            auth: 'bearer'
          };

          // initialize node-ews
          const options = {
            rejectUnauthorized: false,
            strictSSL: false
          };
          // initialize node-ews
          const ews = new EWS(ewsConfig, options);
vhipe2zx

vhipe2zx1#

. We are currently using the OAuth token (access token) from Graph Explorer to test.

图形资源管理器令牌不具有仅适用于EWS的Graph权限,在EWS中有效的两个权限只有EWS.AccessAsUser.All或full_access_as_app(如果使用客户端凭据流)。https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth Mail.Read等权限在EWS中不起作用,因为它不支持Graph支持的更具限制性的身份验证方案(这是在EWS上使用Graph的原因之一)
如果要访问token以使用进行测试,请使用EWSEditor https://github.com/dseph/EwsEditor/releases并获取其令牌

ffvjumwh

ffvjumwh2#

第1-1部分-在AZURE中设置允许为EWS生成MSAL访问令牌的应用程序:

1.登录MS AZURE门户网站。
1.打开“App注册”工具:step2_img
1.点击“新注册”:step3_img
1.设置新应用程序:step4_img
1.点击注册按钮后,您将收到这样的短信:step5_img
1.打开之前创建的应用程序的API权限选项卡,然后单击添加权限并选择MS Graph:step6_img
1.选择委托权限:step7_img
1.查找用户部分并选择用户。读取+添加权限单击:step8_img
1.再次添加权限+“我的组织使用的API”选项卡(或找到它)并找到Office 365 Exchange Online:step9_img
第一部分第二部分继续...

mnemlml8

mnemlml83#

第2部分-通过使用userName + userPassword将accessToken发送到电子邮件箱:

import * as path from 'path';
import { ExchangeService, EmailMessage, MessageBody, OAuthCredentials, AutodiscoverService, Folder, Item, ExchangeVersion } from 'ews-javascript-api';

public async getEmailAccessToken(
       clientId: string,
        tenantId: string,
        emailUserName: string,
        emailUserPassword: string,
        cacheFilePath: string = `.${path.sep}tokenCache.json`) {

        const msal = require('@azure/msal-node');
        const { promises: fs } = require('fs');

        //Cache Plugin configuration         
        const beforeCacheAccess = async (cacheContext) => {
            try {
                const cacheFile = await fs.readFile(cacheFilePath, 'utf-8');
                cacheContext.tokenCache.deserialize(cacheFile);
            } catch (error) {
                // if cache file doesn't exists, create it
                cacheContext.tokenCache.deserialize(await fs.writeFile(cacheFilePath, ''));
            }
        };

        const afterCacheAccess = async (cacheContext) => {
            if (cacheContext.cacheHasChanged) {
                try {
                    await fs.writeFile(cacheFilePath, cacheContext.tokenCache.serialize());
                } catch (error) {
                    console.log(error);
                }
            }
        };

        const cachePlugin = {
            beforeCacheAccess,
            afterCacheAccess
        };

        const msalConfig = {
            auth: {
                clientId: clientId, // YOUR clientId
                authority: `https://login.microsoftonline.com/${tenantId}` // YOUR tenantId
            },
            cache: {
                cachePlugin
            },
            system: {
                loggerOptions: {
                    loggerCallback(loglevel, message, containsPii) {
                        console.log(message);
                    },
                    piiLoggingEnabled: false,
                    logLevel: msal.LogLevel.Verbose
                }
            }
        };

        const pca = new msal.PublicClientApplication(msalConfig);

        const msalTokenCache = pca.getTokenCache();

        const accounts = await msalTokenCache.getAllAccounts();

        // Acquire Token Silently if an account is present
        let accessToken = null;

        if (accounts.length > 0) {
            const silentRequest = {
                account: accounts[0], // Index must match the account that is trying to acquire token silently
                scopes: ['https://outlook.office365.com/EWS.AccessAsUser.All'],
            };

            const response = await pca.acquireTokenSilent(silentRequest);

            accessToken = response.accessToken;
        } else {
            // fall back to username password if there is no account
            const usernamePasswordRequest = {
                scopes: ['https://outlook.office365.com/EWS.AccessAsUser.All'],
                username: emailUserName, // Add your username here      
                password: emailUserPassword, // Add your password here
            };

            const response = await pca.acquireTokenByUsernamePassword(usernamePasswordRequest);

            accessToken = response.accessToken;
        }

        return accessToken;
    }

此方法返回accessToken,允许我们使用EWS-api,还生成tokenCacheFile.json,在多次调用的情况下,它将用于静默使用。

第3部分-使用先前生成的accessToken和ews-javascript-API连接到电子邮箱:

import { ExchangeService, EmailMessage, MessageBody, OAuthCredentials, AutodiscoverService, Folder, Item, ExchangeVersion } from 'ews-javascript-api';

public async connectAndChangeAllEmailsFromBlaBla(
        clientId: string,
        tenantId: string,
        exchangeServiceUrl: string = 'https://outlook.office365.com/Ews/Exchange.asmx',
        emailUserName: string,
        emailUserPassword: string,
        searchMask: string = 'hasattachments:yes and from:NoReply@blabla.com and received:today') {
        
        // get acces token by method written above in part 2
        const emailAccessToken = await this.getEmailAccessToken(clientId, tenantId, emailUserName, emailUserPassword);

        const ews = require('ews-javascript-api');
        const service = new ExchangeService(ews.ExchangeVersion.Exchange2013);

        // use emailAccesToken
        service.Credentials = new OAuthCredentials(emailAccessToken);

        service.Url = new ews.Uri(exchangeServiceUrl);

        const mailInbox = await ews.Folder.Bind(service, ews.WellKnownFolderName.Inbox);
        const loadPageSize = 1000; // 1 means load last email according to filter

        const view = new ews.ItemView(loadPageSize);
        view.PropertySet = new ews.PropertySet(ews.BasePropertySet.FirstClassProperties);
        let mailItems;
        // hasattachment:yes
        // isread:false
        // received:today or received:[date]
        mailItems = await mailInbox.FindItems(searchMask, view);
        console.log(`Emails were found before processing: ${mailItems.Items.length}`);

        for (const item of mailItems.Items) {
            // mark mail.item as read
            item.IsRead = true;
            await item.Update(1);
            // Do what you want
        }

        return mailItems.Items.length;
    }
jk9hmnmh

jk9hmnmh4#

第0部分-请查找我们用于修复相同问题的解决方案。

溶液由3部分组成:
1.在AZURE中设置允许为EWS生成MSAL访问令牌的应用程序。
1.添加代码以获取accessToken。
1.在旧代码中进行了更改,以使用以前收到的accessToken。我使用的是ews-javascript-API。但我认为前面的两个步骤将帮助您获得EWS的accessToken,您可以将其用于节点EWS。
对不起3职位,但作为一个新用户,我有一个限制,它不可能为新用户创建超过8个链接等职位...)

ej83mcc0

ej83mcc05#

    • 第1 - 2部分-续:**

1.找到EWS部分并选择EWS. AccessAsUser. All,然后单击添加权限:step10_img
1.转到"身份验证"选项卡,然后单击"添加平台":step11_img
1.选择移动和桌面应用程序,然后单击保存按钮:step12_img
1.选择两个选项并单击配置:step13-1_imgstep13-2_img
1.另外,在身份验证选项卡上设置"支持的帐户类型"和"允许公共客户端流",然后单击保存:step14_img
1.转到"概述"选项卡,您应该看到如下所示的smthg:客户端ID租户ID step15_img
1.这一步应该由每个用户,将使用此API-使用用户凭据打开此链接(或您的管理员应批量应用).检查所做的更改,打开下一个链接在浏览器中的隐身模式(为每个用户):

https://login.microsoftonline.com/ADD YOUR TENANTID/oauth2/v2.0/authorize?
client_id=ADD YOUR CLIENTID
&response_type=code
&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
&response_mode=query
&scope=EWS.AccessAsUser.All
&state=12345

1.打开之前生成的链接后,您应该登录,然后在浏览器中接收另一个链接,其中shoud包含生成的代码:step16_img
1.现在,我们可以开始添加代码,以获取accessToken

相关问题