Не удалось получить токен без вывода сообщений с помощью WebApp после входа в систему с помощью Azure AD? - PullRequest
0 голосов
/ 10 октября 2018

У меня есть WebApp и WebAPI, зарегистрированные в одной и той же Azure AD.

Я пытаюсь вызвать WebAPI из WebApp.

Я добавил служебный WebAPI в свое WebApp в Azure AD Applcaition.как показано ниже - enter image description here

Когда я запускаю WebAPI, после успешного входа в систему открывается экран входа в систему, и я могу получить доступ к методам WebAPI.Это нормальное поведение.

Когда я запускаю WebApp, он будет работать на том же экране входа в систему, и после успешного входа в систему я вижу WebApp.

Теперь я хочу вызывать методы WebAPI из WebApp, но я не хочуэкран входа в систему для 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" };
        }
    }

код WebApp -

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;
        }
    }

HomeController.cs

[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();
        }
    }

AdalTokenCache.cs - одинаково как для WebApp, так и для WebAPI

 public class ADALTokenCache : TokenCache
    {
        private ApplicationDbContext db = new ApplicationDbContext();
        private string userId;
        private UserTokenCache Cache;

        public ADALTokenCache(string signedInUserId)
        {
            // associate the cache to the current user of the web app
            userId = signedInUserId;
            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            this.BeforeWrite = BeforeWriteNotification;
            // look up the entry in the database
            Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            // place the entry in memory
            this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
        }

        // clean up the database
        public override void Clear()
        {
            base.Clear();
            var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            db.UserTokenCacheList.Remove(cacheEntry);
            db.SaveChanges();
        }

        // Notification raised before ADAL accesses the cache.
        // This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
        void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            if (Cache == null)
            {
                // first time access
                Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            }
            else
            { 
                // retrieve last write from the DB
                var status = from e in db.UserTokenCacheList
                             where (e.webUserUniqueId == userId)
                select new
                {
                    LastWrite = e.LastWrite
                };

                // if the in-memory copy is older than the persistent copy
                if (status.First().LastWrite > Cache.LastWrite)
                {
                    // read from from storage, update in-memory copy
                    Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
                }
            }
            this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
        }

        // Notification raised after ADAL accessed the cache.
        // If the HasStateChanged flag is set, ADAL changed the content of the cache
        void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if state changed
            if (this.HasStateChanged)
            {
                if (Cache == null)
                {
                    Cache = new UserTokenCache
                    {
                        webUserUniqueId = userId
                    };
                }

                Cache.cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache");
                Cache.LastWrite = DateTime.Now;

                // update the DB and the lastwrite 
                db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
                db.SaveChanges();
                this.HasStateChanged = false;
            }
        }

        void BeforeWriteNotification(TokenCacheNotificationArgs args)
        {
            // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
        }

        public override void DeleteItem(TokenCacheItem item)
        {
            base.DeleteItem(item);
        }
    }   

Самое важное Я обнаружил, что webapi также имеет AccountController с тем же кодом для знака нижев как веб-приложение сделать.что делать в таком случае?

 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();
        }
    }

1 Ответ

0 голосов
/ 11 октября 2018

AcquireTokenSilentAsync метод может помочь вам только в том случае, если вы только если ваше приложение уже приобрело действительный токен для целевого ресурса (в вашем случае backend Web API) хотя бы один раз и кэшировал этот токен для последующего использования.

Вероятно, вы получаете эту ошибку, потому что вы ни разу не аутентифицировались в Web API (то есть приобрели действительный токен для Web API и передали его хотя бы один раз), поэтому из кеша ничего нельзя использовать,

Проще говоря, вы не сможете использовать AcquireTokenSilentAsync для аутентификации в первый раз.

Для дальнейшего понимания посмотрите на пример GitHub, которым вы поделились, как часть самого вашего вопроса. Защитите бэкэнд-веб-API

  • В примере кода сначала получается действительный токен для веб-API с использованием потока кода авторизации.

  • Только после появления первого действующего токена он кэшируется, и последующие вызовы могут обрабатываться authContext.AcquireTokenSilentAsync.Это также четко указано в качестве примера документации.

  • resourceID.URI идентификатора приложения веб-API, который вы создали при регистрации веб-API в Azure AD

  • tokenCache.Объект, который кэширует токены доступа.См. Кэширование токена.

Если AcquireTokenByAuthorizationCodeAsync завершается успешно, ADAL кэширует токен.Позже вы можете получить токен из кэша, вызвав AcquireTokenSilentAsync

Код из образца

  1. Чтобы получить действительный токен в первый разиспользование потока кода авторизации

    // Промежуточное программное обеспечение 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
    }
    
  2. Позже вы можете получить токен из кэша, вызвав AcquireTokenSilentAsync:

    AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache);
    var result = await authContext.AcquireTokenSilentAsync(resourceID, credential, new UserIdentifier(userId, UserIdentifierType.UniqueId));
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...