将本机客户端身份验证为Azure AD安全MVC web应用程序时出现问题



我创建了一个托管在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方法中所做的事情。原来我把开发和测试设置搞混了。

也许这将帮助其他人谁结束了这个令人困惑的错误消息。

最新更新