ADAL .Net Core nuget 包不支持 UserPasswordCredential
Posted
技术标签:
【中文标题】ADAL .Net Core nuget 包不支持 UserPasswordCredential【英文标题】:ADAL .Net Core nuget package does not support UserPasswordCredential 【发布时间】:2017-01-17 09:22:43 【问题描述】:在 ADAL.Net 3.x 中,在 2.x 的 UserCredential 之上引入了 UserPasswordCredential。但是相同的UserPasswordCredential没有暴露在同一个nuget包下的.Net Core中?
UserCredential 类只有一个属性 UserName
namespace Microsoft.IdentityModel.Clients.ActiveDirectory
//
// Summary:
// Credential used for integrated authentication on domain-joined machines.
public class UserCredential
//
// Summary:
// Constructor to create user credential. Using this constructor would imply integrated
// authentication with logged in user and it can only be used in domain joined scenarios.
public UserCredential();
//
// Summary:
// Constructor to create credential with client id and secret
//
// Parameters:
// userName:
// Identifier of the user application requests token on behalf.
public UserCredential(string userName);
//
// Summary:
// Gets identifier of the user.
public string UserName get;
由于.NetCore中没有UserPasswordCredential,而且UserCredential只有一个参数username,那么如何在.Net Core中输入用户的密码并实现以下代码?
authContext.AcquireTokenAsync(WebAPIResourceId, ClientId, userPasswordCredential);
我在 .Net Core 1.0 版本中专门使用 ADAL 3.13.4 版本
【问题讨论】:
ADAL.NET v3 does not support AcquireToken with UserCredential?的可能重复 UserPasswordCredential 在相同包和相同版本下的 .NET Core 中不可用 【参考方案1】:要使用资源所有者密码凭据授予流程来获取 Azure AD 的访问令牌,我们可以使用 HttpClient 直接调用 http 请求。这是一个示例供您参考:
HttpClient client = new HttpClient();
string tokenEndpoint = "https://login.microsoftonline.com/tenantId/oauth2/token";
var body = "resource=resourceUrl&client_id=clientId&grant_type=password&username=userName&password=password";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result=await client.PostAsync(tokenEndpoint, stringContent).ContinueWith<string>((response) =>
return response.Result.Content.ReadAsStringAsync().Result;
);
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
【讨论】:
看来client_secret也是必需的 这取决于应用程序的类型。在这种情况下,Web 应用程序需要 client_secret。 明白了,谢谢。另外值得注意的是,所有值都应在正文中进行 URI 编码。 这对我有用。正如@FeiXue-MSFT 提到的,需要注意的是,您使用的client_id
必须配置为 Azure 中的本机客户端应用程序。
我似乎无法完成这项工作。无论我尝试对请求做什么,除了请求凭据的网页之外,我都无法让端点返回任何内容。在这种情况下,我的端点是login.windows.net/common/oauth2/authorize。【参考方案2】:
你是对的,UserPasswordCredential
is not available 用于 .NET Core,UserCredential
不再接受用户名和密码。这意味着 ADAL v3 不支持 .NET Core 上的用户名/密码流。
【讨论】:
并用评论"This is not supported by design."关闭错误 @PhilippeSignoret 那么MSAL.NET
中的替代方案是什么?例如,我曾经使用this 代码获取令牌。但由于我使用的是.NET Core 3.1
和MSAL.NET
,我不能使用该代码,因为它使用的是来自ADAL.NET
的UserPasswordCredential
。
你真的不应该使用用户名/密码流。如果出于某种原因你真的认为你确实需要这个:github.com/AzureAD/microsoft-authentication-library-for-dotnet/…【参考方案3】:
以下是我为解决此问题所做的工作。我在 .NET Core 中使用的静态方法中复制了相同的行为,因为缺少 UserPasswordCredential 类。这是基于提琴手跟踪在 .NET 版本中使用 UserPasswordCredential 类时发生的情况。由于 .NET DLL 似乎被混淆了,因此这是捕获其作用的最佳尝试。
public const string Saml11Bearer = "urn:ietf:params:oauth:grant-type:saml1_1-bearer";
public const string Saml20Bearer = "urn:ietf:params:oauth:grant-type:saml2-bearer";
public const string JwtBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer";
/// <summary>
/// Acquire an AAD authentication token silently for an AAD App (Native) with an AAD account
///
/// NOTE: This process was ported from the Microsoft.IdentityModel.Clients.ActiveDirectory's
/// AuthenticationContext.AcquireTokenAsync method, which can silently authenticate using the UserPasswordCredential class.
/// Since this class is missing from .NET Core, this method can be used to perform the same without any dependencies.
/// </summary>
/// <param name="user">AAD login</param>
/// <param name="pass">AAD pass</param>
/// <param name="tenantId">Tenant ID</param>
/// <param name="resourceUrl">Resource ID: the Azure app that will be accessed</param>
/// <param name="clientId">The Application ID of the calling app. This guid can be obtained from Azure Portal > app auth setup > Advanced Settings</param>
public static string GetAuthTokenForAADNativeApp(string user, SecureString pass, string tenantId, string resourceUrl, string clientId)
string tokenForUser = string.Empty;
string authority = "https://login.microsoftonline.com/" + tenantId; // The AD Authority used for login
string clientRequestID = Guid.NewGuid().ToString();
// Discover the preferred openid / oauth2 endpoint for the tenant (by authority)
string api = "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=" + authority + "/oauth2/authorize";
string openIdPreferredNetwork = string.Empty;
var client = new HttpClient();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
var responseTask = client.GetAsync(api);
responseTask.Wait();
if (responseTask.Result.Content != null)
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
dynamic json = JObject.Parse(responseString.Result);
openIdPreferredNetwork = json.metadata[0].preferred_network; // e.g. login.microsoftonline.com
catch
if (string.IsNullOrEmpty(openIdPreferredNetwork))
openIdPreferredNetwork = "login.microsoftonline.com";
// Get the federation metadata url & federation active auth url by user realm (by user domain)
responseTask = client.GetAsync("https://" + openIdPreferredNetwork + "/common/userrealm/" + user + "?api-version=1.0");
responseTask.Wait();
string federation_metadata_url = string.Empty;
string federation_active_auth_url = string.Empty;
if (responseTask.Result.Content != null)
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
dynamic json = JObject.Parse(responseString.Result);
federation_metadata_url = json.federation_metadata_url; // e.g. https://sts.domain.com.au/adfs/services/trust/mex
federation_active_auth_url = json.federation_active_auth_url; // e.g. https://sts.domain.com.au/adfs/services/trust/2005/usernamemixed
catch
if(string.IsNullOrEmpty(federation_metadata_url) || string.IsNullOrEmpty(federation_active_auth_url))
return string.Empty;
// Get federation metadata
responseTask = client.GetAsync(federation_metadata_url);
responseTask.Wait();
string federationMetadataXml = null;
if (responseTask.Result.Content != null)
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
federationMetadataXml = responseString.Result;
catch
if (string.IsNullOrEmpty(federationMetadataXml))
return string.Empty;
// Post credential to the federation active auth URL
string messageId = Guid.NewGuid().ToString("D").ToLower();
string postData = @"
<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
<s:Header>
<a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
<a:MessageID>urn:uuid:" + messageId + @"</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand='1'>" + federation_active_auth_url + @"</a:To>
<o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
<u:Timestamp u:Id='_0'>
<u:Created>" + DateTime.Now.ToString("o") + @"</u:Created>
<u:Expires>" + DateTime.Now.AddMinutes(10).ToString("o") + @"</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id='uuid-" + Guid.NewGuid().ToString("D").ToLower() + @"'>
<o:Username>" + user + @"</o:Username>
<o:Password>" + FromSecureString(pass) + @"</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust='http://schemas.xmlsoap.org/ws/2005/02/trust'>
<wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
<a:EndpointReference>
<a:Address>urn:federation:MicrosoftOnline</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</trust:KeyType>
<trust:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
var content = new StringContent(postData, Encoding.UTF8, "application/soap+xml");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("SOAPAction", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue");
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
responseTask = client.PostAsync(federation_active_auth_url, content);
responseTask.Wait();
XmlDocument xml = new XmlDocument();
string assertion = string.Empty;
string grant_type = string.Empty;
if (responseTask.Result.Content != null)
HttpResponseMessage rseponse = responseTask.Result;
Task<string> responseContentTask = rseponse.Content.ReadAsStringAsync();
responseContentTask.Wait();
try xml.LoadXml(responseContentTask.Result);
catch
var nodeList = xml.GetElementsByTagName("saml:Assertion");
if (nodeList.Count > 0)
assertion = nodeList[0].OuterXml;
// The grant type depends on the assertion value returned previously <saml:Assertion MajorVersion="1" MinorVersion="1"...>
grant_type = Saml11Bearer;
string majorVersion = nodeList[0].Attributes["MajorVersion"] != null ? nodeList[0].Attributes["MajorVersion"].Value : string.Empty;
if (majorVersion == "1")
grant_type = Saml11Bearer;
if (majorVersion == "2")
grant_type = Saml20Bearer;
else
grant_type = Saml11Bearer; // Default to Saml11Bearer
// Post to obtain an oauth2 token to for the resource
// (*) Pass in the assertion XML node encoded to base64 in the post, as is done here https://blogs.msdn.microsoft.com/azuredev/2018/01/22/accessing-the-power-bi-apis-in-a-federated-azure-ad-setup/
UserAssertion ua = new UserAssertion(assertion, grant_type, Uri.EscapeDataString(user));
UTF8Encoding encoding = new UTF8Encoding();
Byte[] byteSource = encoding.GetBytes(ua.Assertion);
string base64ua = Uri.EscapeDataString(Convert.ToBase64String(byteSource));
postData = "resource=resourceUrl&client_id=clientId&grant_type=grantType&assertion=assertion&scope=openid"
.Replace("resourceUrl", Uri.EscapeDataString(resourceUrl))
.Replace("clientId", Uri.EscapeDataString(clientId))
.Replace("grantType", Uri.EscapeDataString(grant_type))
.Replace("assertion", base64ua);
content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("client-request-id", clientRequestID);
client.DefaultRequestHeaders.Add("return-client-request-id", "true");
client.DefaultRequestHeaders.Add("Accept", "application/json");
responseTask = client.PostAsync("https://" + openIdPreferredNetwork + "/common/oauth2/token", content);
responseTask.Wait();
if (responseTask.Result.Content != null)
var responseString = responseTask.Result.Content.ReadAsStringAsync();
responseString.Wait();
try
dynamic json = JObject.Parse(responseString.Result);
tokenForUser = json.access_token;
catch
if (string.IsNullOrEmpty(federationMetadataXml))
return string.Empty;
return tokenForUser;
private static string FromSecureString(SecureString value)
string stringBSTR;
IntPtr bSTR = Marshal.SecureStringToBSTR(value);
if (bSTR == IntPtr.Zero)
return string.Empty;
try
stringBSTR = Marshal.PtrToStringBSTR(bSTR);
finally
Marshal.FreeBSTR(bSTR);
return stringBSTR;
【讨论】:
我尝试使用此方法,但federation_metadata_url
和 federation_active_auth_url
在预期的响应中缺失。我得到以下 json:"ver":"1.0","account_type":"Managed","domain_name":"mydomain.onmicrosoft.com","cloud_instance_name":"microsoftonline.com","cloud_audience_urn":"urn:federation:MicrosoftOnline"
。你知道出了什么问题吗?
登录到 portal.azure.com (sts...) 时通常会重定向到哪个 URL?尝试对其进行硬编码 (federation_active_auth_url) 以查看它是否有效(例如,类似于“sts.domain.com.au/adfs/...”,因此创建一个像这样的硬编码字符串:“@987654323 @.domain.com.au/adfs/services/trust/2005/usernamemixed”)?您可以注释掉使用 federation_metadata_url 的部分,因为此查询的响应不会被输入任何内容。我的猜测是您的租户似乎没有关联的域名,因此可能会默认为通用 MS sts 页面。【参考方案4】:
快进到 2020 年,借助 ADAL 3.19.8,您应该能够使用 ClientCredential 类进行 AAD 身份验证。与 D365 CRM Web API 集成时,它对我有用。我在以下blog post 中记录了我的经验。希望你会发现它有用。
【讨论】:
问题是询问如何使用用户名/密码,例如resource owner password credential flow。不是客户端凭据流。以上是关于ADAL .Net Core nuget 包不支持 UserPasswordCredential的主要内容,如果未能解决你的问题,请参考以下文章
VS Code Intellisense对nuget包不起作用