Active Directory 身份验证库程序化 SSO

Active Directory Authentication Library Programmatic SSO

我们正在使用 ADAL C# 库获取 Azure AD 应用程序的令牌(包括图形 API)。

我们使用本地 ADFS 3.0 服务器为 Azure AD 设置了 AD Connect。因此,当本机客户端应用程序调用....

var ourDomain = "abc.com";
var authority = "https://login.microsoftonline.com/" + ourDomain;
var authenticationContext = new AuthenticationContext(authority);

var graphResourceUri = "https://graph.windows.net";
var azureADApplicationClientId = "7718c738-0000-0000-0000-4382476f1c65";

var result = authenticationContext.AcquireToken(
   graphResourceUri, 
   azureADApplicationClientId, new Uri("https://localhost"),
   PromptBehavior.RefreshSession, 
   new UserIdentifier($"jdoe@{ourDomain}", UserIdentifierType.RequiredDisplayableId), 
   $"domain_hint={ourDomain}");

弹出 MSOnline 登录 window,立即重定向到我们位于

的 ADFS 服务器
https://ouradfs.abc.com/adfs/ls/wia?username=.......................

然后立即关闭,因为 NTLM(Windows 集成)身份验证发生并成功...并且 result 包含我们[上登录的 Windows 用户的有效访问令牌=18=] 域名.

尽管此过程不需要任何点击...它仍然是 "interactive"。我希望能够利用 Windows 集成身份验证以编程方式在非交互式环境(例如 Windows 服务或计划任务)中获取此类访问令牌。这样我就不必在我们的 Windows 服务或计划任务中嵌入客户端机密。

这可能吗?

更新: 这是代码行

var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/mycorpdev.onmicrosoft.com");

AuthenticationResult result = authenticationContext.AcquireToken(
  "https://graph.windows.net", 
  nativeApplicationClientId, 
  new Uri("https://localhost"),
  PromptBehavior.Never, 
  new UserIdentifier("jsmith@mycorp.com"), UserIdentifierType.RequiredDisplayableId), $"domain_hint=mycorp.com");

以及准确的错误:

Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException occurred
  ErrorCode=user_interaction_required
  HResult=-2146233088
  Message=user_interaction_required: One of two conditions was encountered: 1. The PromptBehavior.Never flag was passed, but the constraint could not be honored, because user interaction was required. 2. An error occurred during a silent web authentication that prevented the http authentication flow from completing in a short enough time frame
  Source=Microsoft.IdentityModel.Clients.ActiveDirectory
  StackTrace:
       at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.RunAsyncTask[T](Task`1 task)

另一项更新:

这有效(客户端 ID 和租户与用户的家庭租户相同)

var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/mycorp.onmicrosoft.com");

AuthenticationResult result = authenticationContext.AcquireToken(
  "https://graph.windows.net", 
  nativeApplicationClientIdFromHomeTenantNotOtherTenant, 
  new Uri("https://localhost"),
  PromptBehavior.Never, 
  new UserIdentifier("jsmith@mycorp.com"), UserIdentifierType.RequiredDisplayableId), $"domain_hint=mycorp.com");

更新 - 这是来自 Fiddler 的捕获

GET https://login.microsoftonline.com/mycorpdev.onmicrosoft.com/oauth2/authorize?resource=https%3A%2F%2Fgraph.windows.net&client_id=78ebfdee-8144-48f8-9a96-1bd5418c0492&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%2F&login_hint=jsmith%40mycorp.COM&client-request-id=7a8878d4-2762-4784-9d29-6f49b147d474&prompt=attempt_none&x-client-SKU=.NET&x-client-Ver=2.19.0.0&x-client-CPU=x64&x-client-OS=Microsoft+Windows+NT+6.1.7601+Service+Pack+1&domain_hint=mycorpdev.onmicrosoft.com HTTP/1.1
Accept: */*
Accept-Language: en-US
UA-CPU: AMD64
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/7.0; .NET CLR 2.0.50727; SLCC2; Media Center PC 6.0; Tablet PC 2.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E)
Host: login.microsoftonline.com
Connection: Keep-Alive
Cookie: ESTSAUTHPERSISTENT=AAABAAEAiL9Kn2Z27UubvWFPbm0gLfZQz8hXb5bXR-iThmkV-FLuLx102LOLAWrogj3rvf40Xl0xjZntMo0Kzvbo0x38Z2CpfCjtOwyyVpp1DWlxyyRPBbl4Z4da5pFuYjfCLPqExGUvo5gBoMdeQ-0MobfbSV2GQCHgbL1CFRjOu6YJZUEgnk7Vyls4rOlHGaqEGpzm5OeFQj3acldcvD9C4PX1gGsV-2g5GU8Frx3co4YzqYHMbhp6fgzf18sfgWaaG9caWj756P2oDvqe9qAlehXk51cA2AUacM2h-k2rtN8F341p7tnOFkNBzCj_E4z3bTnwHodimoXLiDlDWjFWkzAANyAA


HTTP/1.1 302 Found
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Expires: -1
Location: https://localhost/?error=login_required&error_description=AADSTS50058%3a+User+account+identifier+is+not+provided.%0d%0aTrace+ID%3a+133f0405-eb3d-452d-a8b6-b6ba6267af7c%0d%0aCorrelation+ID%3a+7a8878d4-2762-4784-9d29-6f49b147d474%0d%0aTimestamp%3a+2016-02-21+04%3a54%3a07Z
Vary: Accept-Encoding
Server: Microsoft-IIS/8.5
x-ms-request-id: 133f0405-eb3d-452d-a8b6-b6ba6267af7c
client-request-id: 7a8878d4-2762-4784-9d29-6f49b147d474
x-ms-gateway-service-instanceid: ESTSFE_IN_420
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: flight-uxoptin=true; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=productionb; path=/; secure; HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Sun, 21 Feb 2016 04:54:05 GMT
Content-Length: 0

再更新一次...

如果我尝试使用集成身份验证(注意它确实会尝试正确重定向到我们的 ADFS...):

var authenticationContext = new AuthenticationContext(""https://login.microsoftonline.com/" + UserPrincipal.Current.UserPrincipalName.Split('@')[1]), false);
var nativeClientId = "00000000-0f32-4c38-bdb9-4ea5bd732c69";
var token = authenticationContext.AcquireTokenAsync(Constants.ReportingApplicationUri, nativeClientId, new UserCredential()).Result;

System.AggregateException occurred
  HResult=-2146233088
  Message=One or more errors occurred.
  Source=mscorlib
  StackTrace:
       at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
:line 68
  InnerException: 
       ErrorCode=federated_service_returned_error
       HResult=-2146233088
       Message=Federated service at https://ds1.mycorp.com/adfs/services/trust/2005/windowstransport returned error: The message with Action 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver.  Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).
       Source=Microsoft.IdentityModel.Clients.ActiveDirectory
       StatusCode=500
       StackTrace:
            at Microsoft.IdentityModel.Clients.ActiveDirectory.WsTrustRequest.<SendRequestAsync>d__1.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenNonInteractiveHandler.<PreTokenRequest>d__4.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<RunAsync>d__0.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.<AcquireTokenCommonAsync>d__0.MoveNext()
         --- End of stack trace from previous location where exception was thrown ---
            at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
            at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
            at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.<AcquireTokenAsync>d__14.MoveNext()
       InnerException: 
            HResult=-2146233079
            Message=The remote server returned an error: (500) Internal Server Error.
            Source=System
            StackTrace:
                 at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
                 at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
              --- End of stack trace from previous location where exception was thrown ---
                 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
                 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
                 at Microsoft.IdentityModel.Clients.ActiveDirectory.HttpWebRequestWrapper.<GetResponseSyncOrAsync>d__2.MoveNext()
              --- End of stack trace from previous location where exception was thrown ---
                 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
                 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
                 at Microsoft.IdentityModel.Clients.ActiveDirectory.WsTrustRequest.<SendRequestAsync>d__1.MoveNext()
            InnerException: 

当然可以。您可以通过 2 种替代方式进行。

  1. 通过PromptBehavior.Never。这将使用一个不可见的浏览器,使操作 100% 非交互式。这适用于 Kerberos 和任何其他会话类型(例如 cookie)。
  2. 使用接受 UserCredentialAcquireToken 重载。传递给它一个空的 UserCredential,如 new UserCredential()。这将强制使用 Kerberos 身份验证。