Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/305.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在使用Azure AD登录后使用WebApp以静默方式获取令牌失败?_C#_.net_Azure_Azure Active Directory - Fatal编程技术网

C# 在使用Azure AD登录后使用WebApp以静默方式获取令牌失败?

C# 在使用Azure AD登录后使用WebApp以静默方式获取令牌失败?,c#,.net,azure,azure-active-directory,C#,.net,Azure,Azure Active Directory,我已将WebApp和WebAPI注册到同一Azure广告中 我正在尝试从WebApp调用WebAPI 我已在azure AD应用程序中将服务WebAPI添加到我的WebApp中。如下- 当我运行WebAPI时,登录成功后,它会给我一个登录屏幕,我可以访问WebAPI方法。这是正常的行为 当我运行WebApp时,它将显示相同的登录屏幕,登录成功后,我可以看到WebApp 现在我想从WebApp调用WebAPI方法,但我不希望WebAPI出现登录屏幕,因为当我运行WebApp时,我会看到登录屏幕

我已将WebApp和WebAPI注册到同一Azure广告中

我正在尝试从WebApp调用WebAPI

我已在azure AD应用程序中将服务WebAPI添加到我的WebApp中。如下-

当我运行WebAPI时,登录成功后,它会给我一个登录屏幕,我可以访问WebAPI方法。这是正常的行为

当我运行WebApp时,它将显示相同的登录屏幕,登录成功后,我可以看到WebApp

现在我想从WebApp调用WebAPI方法,但我不希望WebAPI出现登录屏幕,因为当我运行WebApp时,我会看到登录屏幕 在登录之后,我希望通过使用同一个用户,我应该能够访问WebAPI,而无需再次进行登录操作,因为我有一个令牌,它将对这两个用户都有效 WebApp和WebAPI

WebAPI代码-

Startup.Auth.cs

public partial class Startup
    {
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
        private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
        private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
        private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];

        public static readonly string Authority = aadInstance + tenantId;

        // This is the resource ID of the AAD Graph API.  We'll need this to request a token to call the Graph API.
        string graphResourceId = "https://graph.windows.net";

        public void ConfigureAuth(IAppBuilder app)
        {
            ApplicationDbContext db = new ApplicationDbContext();

            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;
                           ClientCredential credential = new ClientCredential(clientId, appKey);
                           string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                           AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
                           AuthenticationResult result = authContext.AcquireTokenByAuthorizationCodeAsync(
                               code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId).Result;

                           return Task.FromResult(0);
                       }
                    }
                });
        }

        private static string EnsureTrailingSlash(string value)
        {
            if (value == null)
            {
                value = string.Empty;
            }

            if (!value.EndsWith("/", StringComparison.Ordinal))
            {
                return value + "/";
            }

            return value;
        }
    }
TestController.cs

[Authorize]
    public class TestController : ApiController
    {
        [HttpGet]
        [Route("api/getdata")]
        public IEnumerable<string> GetData()
        {
            return new string[] { "value1", "value2" };
        }
    }
[Authorize]
    public class HomeController : Controller
    {
        private static string clientIdWebApp = ConfigurationManager.AppSettings["ida:clientIdWebApp"];
        private static string clientIdWebApi = ConfigurationManager.AppSettings["ida:clientIdWebApi"];
        private static string clientSecretWebApp = ConfigurationManager.AppSettings["ida:clientSecretWebApp"];
        private static string aadInstance = (ConfigurationManager.AppSettings["ida:AADInstance"]);
        private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
        private static string PostLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
        Uri redirectUri = new Uri(PostLogoutRedirectUri);
        public static readonly string Authority = aadInstance + tenantId;

        public ActionResult Index()
        {          
                return View();
        }

        public async System.Threading.Tasks.Task<ActionResult> About()
        {
            ViewBag.Message = "Your application description page.";
            try
            {
                AuthenticationResult result = null;

                string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
                AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID));
                ClientCredential credential = new ClientCredential(clientIdWebApp, clientSecretWebApp);
                //AcquireTokenSilentAsync should have to work as i'm accessing WebAPI using same user I logged in to WebApp 
                result = authContext.AcquireTokenSilentAsync(clientIdWebApi,credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)).Result;
                // gettign exception {"Failed to acquire token silently as no token was found in the cache. Call method AcquireToken"} but I got match id into cache. 
        // and if use AcquireToken instead then it works but api response is login html //page instead of api output 
                HttpClient client = new HttpClient();
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://MYWEBAPI/api/getdata");
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                HttpResponseMessage response = await client.SendAsync(request);

                // Return the user's profile in the view.
                if (response.IsSuccessStatusCode)
                {
                    string responseString = await response.Content.ReadAsStringAsync();
                }
            }
            catch (AdalException ex)
            {
            }
            return View();
        }
    }
HomeController.cs

[Authorize]
    public class TestController : ApiController
    {
        [HttpGet]
        [Route("api/getdata")]
        public IEnumerable<string> GetData()
        {
            return new string[] { "value1", "value2" };
        }
    }
[Authorize]
    public class HomeController : Controller
    {
        private static string clientIdWebApp = ConfigurationManager.AppSettings["ida:clientIdWebApp"];
        private static string clientIdWebApi = ConfigurationManager.AppSettings["ida:clientIdWebApi"];
        private static string clientSecretWebApp = ConfigurationManager.AppSettings["ida:clientSecretWebApp"];
        private static string aadInstance = (ConfigurationManager.AppSettings["ida:AADInstance"]);
        private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
        private static string PostLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
        Uri redirectUri = new Uri(PostLogoutRedirectUri);
        public static readonly string Authority = aadInstance + tenantId;

        public ActionResult Index()
        {          
                return View();
        }

        public async System.Threading.Tasks.Task<ActionResult> About()
        {
            ViewBag.Message = "Your application description page.";
            try
            {
                AuthenticationResult result = null;

                string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
                AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID));
                ClientCredential credential = new ClientCredential(clientIdWebApp, clientSecretWebApp);
                //AcquireTokenSilentAsync should have to work as i'm accessing WebAPI using same user I logged in to WebApp 
                result = authContext.AcquireTokenSilentAsync(clientIdWebApi,credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)).Result;
                // gettign exception {"Failed to acquire token silently as no token was found in the cache. Call method AcquireToken"} but I got match id into cache. 
        // and if use AcquireToken instead then it works but api response is login html //page instead of api output 
                HttpClient client = new HttpClient();
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://MYWEBAPI/api/getdata");
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
                HttpResponseMessage response = await client.SendAsync(request);

                // Return the user's profile in the view.
                if (response.IsSuccessStatusCode)
                {
                    string responseString = await response.Content.ReadAsStringAsync();
                }
            }
            catch (AdalException ex)
            {
            }
            return View();
        }
    }
最重要的是,我发现webapi也有
AccountController
,下面的代码与webapp一样用于登录。在这种情况下应该怎么做

 public class AccountController : BaseMvcController
    {
        public void SignIn()
        {
            // Send an OpenID Connect sign-in request.
            if (!Request.IsAuthenticated)
            {
                HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
                    OpenIdConnectAuthenticationDefaults.AuthenticationType);
            }
        }

        public void SignOut()
        {
            string callbackUrl = Url.Action("SignOutCallback", "Account", routeValues: null, protocol: Request.Url.Scheme);

            HttpContext.GetOwinContext().Authentication.SignOut(
                new AuthenticationProperties { RedirectUri = callbackUrl },
                OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
        }

        public ActionResult SignOutCallback()
        {
            if (Request.IsAuthenticated)
            {
                // Redirect to home page if the user is authenticated.
                return RedirectToAction("Index", "Home");
            }

            return View();
        }
    }

AcquireTokenSilentAsync
方法只有在应用程序之前至少为目标资源(在您的情况下是后端Web API)获取了一次有效令牌,并且缓存该令牌以供后续使用时,才能对您有所帮助

您可能会遇到此错误,因为您甚至没有对Web API进行过一次真正的身份验证(即获取Web API的有效令牌并传递了一次),因此缓存中没有可用的内容

简单地说,您将无法首次使用AcquireTokenSilentAsync进行身份验证

为了进一步理解,请查看您作为问题本身的一部分共享的GitHub示例

  • 示例代码首先使用授权代码流获取Web API的有效令牌

  • 只有当第一个有效令牌存在时,它才会被缓存,后续调用可以由
    authContext.AcquireTokenSilentAsync
    处理。这也是样本文档的一部分

  • 资源ID。在Azure AD中注册web API时创建的web API的应用ID URI

  • 令牌缓存。缓存访问令牌的对象。请参阅令牌缓存

如果AcquireTokenByAuthorizationCodeAsync成功,ADAL将缓存 代币稍后,您可以通过调用 AcquireTokenSilentAsync

来自样本的代码

  • 使用授权代码流首次获取有效令牌

    //OpenID Connect中间件在获取授权代码时发送此事件

    public override async Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
    {
        string authorizationCode = context.ProtocolMessage.Code;
        string authority = "https://login.microsoftonline.com/" + tenantID
        string resourceID = "https://tailspin.onmicrosoft.com/surveys.webapi" // App ID URI
        ClientCredential credential = new ClientCredential(clientId, clientSecret);
    
        AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
        AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
        authorizationCode, new Uri(redirectUri), credential, resourceID);
    
        // If successful, the token is in authResult.AccessToken
    }
    
  • 稍后,您可以通过调用AcquireTokenSilentAsync从缓存中获取令牌:

    AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
    var result = await authContext.AcquireTokenSilentAsync(resourceID, credential, new UserIdentifier(userId, UserIdentifierType.UniqueId));
    

  • 在哪里可以在MVC应用程序中编写此
    AuthorizationCodeReceived
    ?在Index.cshtml上,我试图使用HttpClient调用同一azure广告中的其他Webapi,而我没有该其他Webapi的clientSecret,我只有客户端id。在这种情况下可以做什么?只有在web应用程序中使用授权代码流时,才能获得该
    AuthorizationCodeReceived
    事件。查看与您在问题中提到的github示例相关联的示例代码。它拥有完整的存储库,包含web应用程序和web api的完整代码。基本上,web应用程序将为用户提供登录屏幕。另外,考虑一下在Web API中是否真的需要用户身份?若api根据用户做出授权决策,那个么您需要api中的用户标识,若不需要,那个么您可以使用应用程序标识。github示例中的代码使用委派的用户标识。github中的代码使用.net核心项目:(我正在使用web mvc项目…是的,我能够在web mvc项目之前获得成功..它将打开登录弹出窗口,当登录时,我在web应用的主页上,即Index.cshtml,现在在这个Index.cshtml上。我想访问注册到同一Azure广告中的web api,我没有这个web api的客户端密码..你能更新代码段吗?嗯..好..代码对您来说会有所不同..但身份验证部分应该类似..顺便说一句..示例代码要查找的秘密不是web api..而是web应用的秘密您要查找的代码片段位于SurveysTokenService.cs..public async Task RequestTokenAsync中(..方法。即使是为GetTokenInforWebPiAsync输入的注释也会将您指向相同的方法。..此方法检索以前///已检索和缓存的WebAPI资源的访问令牌。如果尚未检索和缓存WebAPI///资源的访问令牌,此方法将失败。您可以使用RequestAccessTokenAsync///用于检索和缓存访问令牌的方法。