EWSで基本認証が使えなくなる

2019年7月30日火曜日

t f B! P L

背景

EWS(Exchange Web Services)を利用して、Exchange Serverからメールを取得したり、メールを送信したりするアプリは多く存在するかと思いますが、EWSに対する基本認証が2020年10月13日で廃止されるようです。正確にはOffice365へ移行していたりすると、Office365側が基本認証を受け付けてくれなくなるようです。

解決方法

解決方法としては基本認証からOauth認証に切り替える必要があるようです。
参考URLはこちら
https://blogs.technet.microsoft.com/exchangeteamjp/2018/08/01/how-to-use-oauth-with-ews-in-some-scenarios/

Step.1 AzureADでのアプリ登録

EWSにOauthを利用してアクセスするためにはAzureAD上でアプリ登録を事前に行う必要があります。なぜこのようなアプリ登録が必要かというと、ユーザ名パスワードを知っていればどこからでもアクセスできるという状況を防ぐためと、勝手に理解しています。
イメージとしては今回登録したアプリがOffice365へのアクセス権を持っており、ユーザの代理でOffice365からメールの中身などを取得しに行きます。
詳細な設定方法は下記URLのネイティブアプリの登録を参照ください。



Step.2 Powershellでの実装

Oauth認証を使用してEWSでOffice365のサービスにアクセスするPowershellスクリプトはこちら。赤字の部分は適宜変更が必要です。

<# .SYNOPSIS Acquire the access token for EWS non-interactively. .DESCRIPTION Acquire the access token for EWS non-interactively. .PARAMETER TenantName Tenant name of the user to be used for authentication. .PARAMETER ClientId Client ID of the application registered in Azure Active Directory. .PARAMETER Credential Credential of the user to be used for authentication. .EXAMPLE Get-TokenForEws -TenantName "contoso.onmicrosoft.com" -ClientId "7acfa599-e1bf-43e1-aab8-f12c1d952d3d" -Credential:(Get-Credential) #>
function Get-TokenForEws {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,

        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,

        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential
    )

    $RequestBody = @{
        resource   = "https://outlook.office.com/";
        client_id  = $ClientId;
        grant_type = "password";
        username   = $Credential.UserName;
        password   = $Credential.GetNetworkCredential().Password;
        client_secret = 'アプリ登録時にクライアントシークレットをセット'
        scope      = "openid"
    }

    return Invoke-RestMethod -Uri "https://login.microsoftonline.com/$($TenantName)/oauth2/token" -Method Post -Body $RequestBody
}

<# .SYNOPSIS Acquire the access token for EWS non-interactively using the refresh token. .DESCRIPTION Acquire the access token for EWS non-interactively using the refresh token. .PARAMETER TenantName Tenant name of the user to be used for authentication. .PARAMETER ClientId Client ID of the application registered in Azure Active Directory. .PARAMETER RefreshToken The refresh token acquired by Get-TokenForEws Cmdlet. .EXAMPLE Get-TokenForEwsUsingRefreshToken -TenantName "contoso.onmicrosoft.com" -ClientId "7acfa599-e1bf-43e1-aab8-f12c1d952d3d" -RefreshToken $Token1.refresh_token #> function Get-TokenForEwsUsingRefreshToken {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,

        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,

        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$RefreshToken
    )

    $RequestBody = @{
        resource      = "https://outlook.office.com/";
        client_id     = $ClientId;
        grant_type    = "refresh_token";
        refresh_token = $RefreshToken;
        scope         = "openid"
    }

    return Invoke-RestMethod -Uri "https://login.microsoftonline.com/$($TenantName)/oauth2/token" -Method Post -Body $RequestBody
}

<# .SYNOPSIS Check if re-acquiring the access token is needed or not. .DESCRIPTION Check if re-acquiring the access token is needed or not. .PARAMETER Token The return value of Get-TokenForEws Cmdlet. .EXAMPLE ShouldRefresh -Token $Token1 #>
function ShouldRefresh {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [ValidateNotNullOrEmpty()]
        [PSCustomObject]$Token
    )

    $BaseDate = New-Object System.DateTime(1970, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc)
    $UtcNow = (Get-Date).ToUniversalTime() - $BaseDate
    $TimeSpan = $Token.expires_on - $UtcNow.TotalSeconds
    return (($TimeSpan / 60) -le 5 )
}
# 環境に合わせて変更
$DllPath  = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll" # EWS Managed API のパス
$EwsAdmin = "aaa@xxxx.onmicrosoft.com" # EWS 接続に利用するアカウントのユーザー プリンシパル名 (UPN)
$Password = 'xxxxx' # EWS 接続に利用するアカウントのパスワード
$TenantName = "xxxx.onmicrosoft.com" # 接続するテナントの onmicrosoft.com のドメイン名
$ClientId = "アプリ登録後に確認可能" # Azure Active Directory に登録したアプリケーションのアプリケーション ID
$EwsUrl = "https://outlook.office365.com/EWS/Exchange.asmx" # EWS の URL

# EWS Managed API のアセンブリをロード
$Assembly = [Reflection.Assembly]::LoadFile($DllPath)
if ($Assembly -eq $null) {
    exit
}

$Token = Get-TokenForEws -TenantName $TenantName -ClientId $ClientId -Credential:(New-Object System.Management.Automation.PSCredential($EwsAdmin, (ConvertTo-SecureString $Password -AsPlainText -Force)))

# ExchangeService インスタンスの生成
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1, $TimeZone)
$Service.Url = New-Object System.Uri($EwsUrl)
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($Token.access_token)

# 以降、通常と同じ EWS の処理
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

# 処理が長時間におよぶ場合
if (ShouldRefresh -Token $Token) {
    # アクセス トークンの更新が必要
    $Token = Get-TokenForEwsUsingRefreshToken -TenantName $TenantName -ClientId $ClientId -RefreshToken $Token.refresh_token
    $Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($Token.access_token)
}

# 処理を継続
#$SentItems = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SentItems)

PV

PVアクセスランキング にほんブログ村

ブログ村

このブログを検索

自己紹介

システムエンジニアとして12年ほど勤めたあと、社内SEに転職しました。 2017年に転職して、2019年に中古マンションを買いました。

リモートデスクトップのプロキシ越え

社内ネットワークからクラウド上のサーバにリモートデスクトップしたい Azureなどのクラウド環境にWindowsOSを立ち上げると、インターネット経由でリモートデスクトップ接続することになります。会社のネットワークからインターネットにアクセスする場合はプロキシサーバーやファイ...

QooQ