我创建了一个托管在Azure上的MVC 5 web应用程序和一个WPF客户端。我的短期目标(如果我能实现这一目标,我将能够实现我的所有用例)如下:
- 在WPF客户端上强制Azure广告身份验证
- 让MVC web应用程序通过Azure Graph API检查在客户端中验证的用户的AD组成员身份
- 将Graph API对象发送回客户端(IUser,Group…)
- 使用组成员身份在控制器上定义授权
我的实际问题如下:用户启动应用程序,并被提示进行身份验证。我想它可以工作,因为我可以显示用户的邮件,并且我有一个访问令牌。用户尝试访问web api控制器,它运行良好用户试图访问另一个用[Authorize]装饰的web api控制器,我得到了一些HTML页面,上面写着:"我们无法让你登录。你的浏览器当前设置为阻止JavaScript。你需要允许JavaScript使用此服务。"
从我在网上搜索的结果来看,这似乎与我的网络应用程序配置不正确有关(我已经尝试在受信任的网站中添加我的网络程序url,我确信我的控制器url是可以的)。我在原生客户端+AAD+MVC上找不到太多文档,所以我真的不知道如何纠正它。
这是我在网络应用程序上的startup.auth.cs:
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string certName = ConfigurationManager.AppSettings["ida:CertName"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphUrl"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
//
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
//
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
#region Certs (not used)
if (certName.Length != 0)
{
// Create a Client Credential Using a Certificate
//
// Initialize the Certificate Credential to be used by ADAL.
// First find the matching certificate in the cert store.
//
X509Certificate2 cert = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
// Place all certificates in an X509Certificate2Collection object.
X509Certificate2Collection certCollection = store.Certificates;
// Find unexpired certificates.
X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
// From the collection of unexpired certificates, find the ones with the correct name.
X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, certName, false);
if (signingCert.Count == 0)
{
// No matching certificate found.
return Task.FromResult(0);
}
// Return the first certificate in the collection, has the right name and is current.
cert = signingCert[0];
}
finally
{
store.Close();
}
// Then create the certificate credential.
ClientAssertionCertificate credential = new ClientAssertionCertificate(clientId, cert);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
#endregion
else
{
// Create a Client Credential Using an Application Key
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
return Task.FromResult(0);
}
}
});
}
}
这是一个控制器,当没有用[Authorize]修饰时可以加入,但在这种情况下,操作会抛出一个null异常(但如果我不能修复它,我会发布另一个问题):
[System.Web.Http.Authorize]
public class UserADGraphController : ApiController
{
[ResponseType(typeof(IUser))]
[System.Web.Http.Route("api/UserADGraphController/GetMyInformations")]
public IHttpActionResult GetMyInformations()
{
try
{
string uID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
if (uID == null)
return Ok("UId null");
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
if (client == null)
return Ok("Client null");
IUser adUser = client.Users.Where(u => u.ObjectId == uID).ExecuteAsync().Result.CurrentPage.SingleOrDefault();
if (adUser == null)
{
return NotFound();
}
return Ok(adUser);
}
catch (Exception e)
{
return Ok(e.Message + " " + e.StackTrace);
}
最后是客户端的相关部分:
在mainviewmodel类中:
#region Azure AD auth properties
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
Uri redirectUri = new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]);
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static string AppServiceResourceId = ConfigurationManager.AppSettings["todo:AppServiceResourceId"];
private static string AppServiceBaseAddress = ConfigurationManager.AppSettings["todo:AppServiceBaseAddress"];
private HttpClient httpClient;
private AuthenticationContext authContext = null;
#endregion
在mainviewmodel构造函数中:
authContext = new AuthenticationContext(authority);
httpClient = new HttpClient();
我的登录方式:
{
AuthenticationResult result = null;
try
{
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
SignInLabelContent = "Connected to azure AD as " + result.UserInfo.DisplayableId;
}
catch (AdalException ex)
{
if (ex.ErrorCode == "user_interaction_required")
{
}
else
{
// An unexpected error occurred.
string message = ex.Message;
if (ex.InnerException != null)
{
message += "Inner Exception : " + ex.InnerException.Message;
}
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(message));
//MessageBox.Show(message);
}
return;
}
}
访问受保护控制器的方法:
IUser me = null;
AuthenticationResult result = null;
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
string authHeader = result.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
//request.Headers.TryAddWithoutValidation("Authorization", authHeader);
//HttpResponseMessage response = await client.SendAsync(request);
//string responseString = await response.Content.ReadAsStringAsync();
//LogManager.log(responseString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(responseString));
HttpResponseMessage response = await httpClient.GetAsync(AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
LogManager.log(jsonString);
me = JsonConvert.DeserializeObject<IUser>(jsonString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(jsonString));
}
在我的例子中,响应的状态代码为200,但jsonString包含一个网页,告诉我javascript已禁用。
如果有人有个主意那就太好了!
谢谢!
如果有人遇到这个问题,我通过以下方式更改我的configureAuth方法来解决它:
var azureADBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = tenant
};
azureADBearerAuthOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = audience
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureADBearerAuthOptions);
此错误消息非常具有误导性。我遇到了同样的问题,发现我的问题实际上是客户端机密/AppURI设置不匹配。
从错误消息中,我认为这是我在ConfigureAuth方法中所做的事情。原来我把开发和测试设置搞混了。
也许这将帮助其他人谁结束了这个令人困惑的错误消息。