Office 365 Rest API - 守护程序周身份验证

9
我正在尝试构建一个Ruby守护进程服务,以访问Office 365 REST API。最近可以通过OAuth“client_credentials”流来实现此操作,详见此博客文章:https://learn.microsoft.com/en-us/archive/blogs/exchangedev/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow
我无法生成有效的访问令牌。令牌终结点会返回给我一个JWT,但是当我使用该令牌时,我收到了401错误并显示以下消息:
“所采用的身份验证方法薄弱,无法允许此应用程序访问。提供的身份验证强度为1,要求为2。”
我理解client_credentials流需要您提供X.509证书,不幸的是,博客文章中的所有示例均为C#。
我正在使用生成的自签名证书和私钥来进行客户端声明,请求令牌时。我按照博客文章中的步骤生成了证书,并更新了清单以使用该证书。
下面是Ruby代码以供参考:
def request_token
  uri = URI.parse("https://login.windows.net/== TENANT-ID ==/oauth2/token?api-version=1.0")
  https = Net::HTTP.new(uri.host, uri.port)

  req = Net::HTTP::Post.new(uri.request_uri)
  req.set_form_data(
    :grant_type    => 'client_credentials',
    :redirect_uri  => 'http://spready.dev',
    :resource      => 'https://outlook.office365.com/',
    :client_id     => '== Client ID ==',
    :client_secret => '== Client secret =='
  )

  https.use_ssl = true
  https.cert = client_cert
  https.key = client_key
  https.verify_mode = OpenSSL::SSL::VERIFY_PEER

  resp = https.start { |cx| cx.request(req) }

  @access_token = JSON.parse(resp.body)
end

显然,为了安全起见,我已删除了某些信息。尽管使用的是 Ruby,但您可以看到我正在使用我的证书验证客户端使用 SSL 连接。

以下是有关错误的更多信息:

"x-ms-diagnostics" => "2000010;
    reason=\"The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.\";
    error_category=\"insufficient_auth_strength\"", 
"x-diaginfo"=>"AM3PR01MB0662", 
"x-beserver"=>"AM3PR01MB0662"

任何帮助将不胜感激。
< p >< em >编辑

对于想在Ruby中进行类似操作的其他人,这是我使用的代码的Gist:https://gist.github.com/NGMarmaduke/a088943edbe4e703129d

该示例使用Rails环境,但应该很容易去除特定于Rails的部分。

记得用正确的值替换YOUR CLIENT ID、TENANT_ID和CERT_THUMBPRINT,并将证书路径和客户端密钥方法指向正确的文件路径。

然后你可以做这样的事情:

mailbox = OfficeAPI.new("nick@test.com")
messages = mailbox.request_messages

嘿 @Nick,你曾经让这个工作过吗?我想看看你的代码。我正在尝试在Ruby中做类似的事情,但即使尝试了Jason的答案所说的方法,也无法使其工作。 - Joel Smith
嘿,Joel,我刚刚在问题中增加了一些细节,并添加了我的代码要点。 - Nick Maher
太好了。非常感谢! - Joel Smith
4个回答

11

请求体中不再需要client_secret,而是需要一个client_assertion。这个过程稍微复杂一些,但这也是你需要证书的原因。

基本上,你需要构建一个 JSON Web Token,并使用 SHA256 哈希函数用你的证书对其进行签名。该令牌将类似于以下内容:

头部:

{ 
  "alg": "RS256",
  "x5t": "..." // THUMBPRINT of Cert
}

有效载荷:

{
  "aud": "https:\\/\\/login.windows.net\\/<The logged in user's tenant ID>\\/oauth2\\/token",
  "exp": 1423168488,
  "iss": "YOUR CLIENT ID",
  "jti": "SOME GUID YOU ASSIGN",
  "nbf": 1423167888,
  "sub": "YOUR CLIENT ID"
}

如果你还在跟着我,现在你需要分别将这两部分进行base64编码,然后用'.'连接它们。现在你应该有:

base64_header.base64_payload

现在你需要使用SHA256哈希算法将该字符串签名,然后用您的证书进行签名。 然后对其进行base64编码和url编码,最后将其附加到该字符串中,这样您就得到:

base64_header.base64_payload.base64_signature

最后,在向令牌终端点发送POST请求时,将此内容作为client_assertion参数包含在内,并且还要包括一个client_assertion_type参数,其值设置为"urn:ietf:params:oauth:client-assertion-type:jwt-bearer":

req.set_form_data(
    :grant_type    => 'client_credentials',
    :redirect_uri  => 'http://spready.dev',
    :resource      => 'https://outlook.office365.com/',
    :client_id     => '== Client ID ==',
    :client_assertion_type => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
    :client_assertion => 'base64_header.base64_payload.base64_signature'
  )

希望这有所帮助!这完全基于我对ADAL的研究,我自己没有在Ruby中进行过测试。


4
好的,再次感谢您,Jason。为了让那些不使用ADAL进行开发的人更好地理解,将其文档化可能会是个好主意。可以考虑更新此处的Azure AD客户端凭据文档:https://msdn.microsoft.com/zh-cn/library/azure/dn645543.aspx - Nick Maher
1
@NickMaher 那个文档肯定需要更新。我已经向微软提出了请求。 - Andrew Thaddeus Martin
@Jason,一个问题:为什么调用Office365 API时需要这个证书,但调用Graph API时不需要?我能够使用客户端凭据授权流程在Graph API中进行clientIdclientSecret的身份验证,但在Office365中却不能。为什么会这样? - Krzysztof Wolny
Exchange Online 服务需要一个证书来完成这个流程,因此我的猜测是,即使使用 Graph,如果您尝试使用密码令牌而不是声明获取访问邮箱、日历或联系人,它也会失败。 - Jason Johnston

5
我刚刚成功地使它工作了,所以我想再提供一些建议。所有的指南都说应该将证书添加到清单文件中,但我在这方面遇到了困难。下面是最终让它工作的方法:
  • In Azure, go to Settings > Management Certificates
  • Upload the public key as a .cer file (google around if you don't know how to convert it). This should be a binary file that your text editor barfs on.
  • Now that it's uploaded, Microsoft will give you the thumbprint. It's in the "Thumbprint" column. But, it's in hex, not base64. So, convert it like this:

    # Hint: use your actual thumbprint, not this fake one
    echo '5292850026FADB09700E7D6C1BCB1CD1F3270BCC' | xxd -r -p | base64
    
  • Finally, use this base64 encoded thumbprint as the value for x5t in the JSON header.


嗯,我看到了指纹,但是列不够宽,所以它被截断并添加省略号。没有明显的方法来扩展信息... 有什么想法吗? - stu
没事了,我搞定了... openssl x509 -in server.cer -fingerprint -noout | tr -d ':' | awk -F"=" '{print $2}' | xxd -r -p | base64 - stu

1

0

只是一些补充:断言中的观众声明与您用令牌请求地址相同。正如Jason正确指出的那样,这是AAD的令牌终结点:https://login.windows.net/{您要获取应用程序令牌的租户}/oauth2/token。nbf和exp是您在Unix epoch时间中创建断言的时间,例如在.net中,您可以执行类似于“WebConvert.EpocTime(DateTime.UtcNow)” 的操作。对于“not before”(nbf),可能需要减去时钟偏差的缓冲区,例如5分钟;对于过期时间(exp),添加一些时间,例如15分钟(因此断言仍然有效)。

这是一个令牌请求的 Fiddler 跟踪(原始): POST https://login.windows.net/0e49ef1f-ca07-45f1-b4c0-ac9409d3e576/oauth2/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded client-request-id: a8108f88-275b-424d-ac28-f675aabe548e return-client-request-id: true x-client-SKU: .NET x-client-Ver: 2.12.0.0 x-client-CPU: x64 x-client-OS: Microsoft Windows NT 6.2.9200.0 Host: login.windows.net Content-Length: 983 Expect: 100-continue Connection: Keep-Alive

resource=https%3A%2F%2Fgraph.windows.net%2F&client_id=f17bb8a5-2bef-4ad5-a83f-cd7113449fc2&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6ImY4S2JVY0xtMnItS2s4b1Z3ZVZYTFU0NzhJcyJ9.eyJhdWQiOiJodHRwczpcL1wvbG9naW4ud2luZG93cy5uZXRcLzBlNDllZjFmLWNhMDctNDVmMS1iNGMwLWFjOTQwOWQzZTU3Nlwvb2F1dGgyXC90b2tlbiIsImV4cCI6MTQyMjk4NDMzNSwiaXNzIjoiZjE3YmI4YTUtMmJlZi00YWQ1LWE4M2YtY2Q3MTEzNDQ5ZmMyIiwianRpIjoiZTI3OTA5YTctZGYwMC00NjBhLTlmZjctOGZkNDExOWVmNTYzIiwibmJmIjoxNDIyOTgzNzM1LCJzdWIiOiJmMTdiYjhhNS0yYmVmLTRhZDUtYTgzZi1jZDcxMTM0NDlmYzIifQ.g9bo4-lxpNJ4kEOMuQxODU-5iakwSVIzyRQEPLdbpuNn_XD4lcvt2yBIWT12EQaUVKkMyqFrDiIh4Oav565-Po7HfhmSPF3URXVj8Kx5lx17Zh0nWiaNkRXEi1vhwswsfjm1o-8B8LGUJTtT6JXTognrueuSL1aEE_-4qSG1y74aoc949Un1pQCjwuBtao4vs4CPJLu9Y9mVbirVRRtiIfxkUMmzf6yfMtuhugoGmrvUYntUo4x6N2fu4LxGjuIs7czyrMMAmDRo-XK4sAhDo5uof10HKb8ETEU8mhObwNZcz86MYHWbZm3Z_HDOwzC9kA_tp6hWqmlJ3c-gLg5VXA&grant_type=client_credentials

希望这能帮到你!祝好运!马蒂亚斯

1
另外请注意:您可以使用 http://jwt.calebb.net 来查看您的断言是否正确,并粘贴您在其中生成/发送的 base64url 编码值。它应该会成功显示像 Jason 上面示例中的内容。 - Matthias Leibmann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接