问题是当我在启动adal.js时设置端点,adal.js似乎将所有出站端点流量重定向到microsofts登录服务.
观察:
> Adal.js会话存储包含两个adal.access.token.key条目.一个用于SPA Azure AD应用程序的客户端ID,另一个用于外部api.只有SPA令牌有一个值.
>如果我不注入$httpProvider到adal.js,那么调用去外部API,我得到一个401回报.
>如果我手动将SPA令牌添加到http头(授权:承载“令牌值”),我得到一个401返回.
我的理论是,adal.js无法为端点检索令牌(可能是因为我在SPA中配置了错误的东西),并且它阻止了端点的流量,因为它无法获取必需的令牌. SPA令牌不能用于API,因为它不包含所需的权限.为什么adal.js没有得到端点的令牌,我该如何解决?
附加信息:
>客户端Azure AD应用程序配置为在API中使用委托权限,并在应用程序清单中使用oauth2AllowImplicitFlow = true.
> API Azure AD应用程序配置为模拟,而oauth2AllowImplicitFlow = true(不要以为这是必需的,但尝试过).它是多租户.
> API被配置为允许所有CORS源代码,并且在使用模拟(混合MVC(Adal.net)Angular)的另一个Web应用程序使用时可以正常工作).
会话存储:
key (for the SPA application): adal.access.token.keyxxxxx-b7ab-4d1c-8cc8-xxx value: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1u... key (for API application): adal.access.token.keyxxxxx-bae6-4760-b434-xxx value:
app.js(Angular和adal配置文件)
(function () { 'use strict'; var app = angular.module('app',[ // Angular modules 'ngRoute',// Custom modules // 3rd Party Modules 'AdalAngular' ]); app.config(['$routeProvider','$locationProvider',function ($routeProvider,$locationProvider) { $routeProvider // route for the home page .when('/home',{ templateUrl: 'App/Features/Test1/home.html',controller: 'home' }) // route for the about page .when('/about',{ templateUrl: 'App/Features/Test2/about.html',controller: 'about',requireADLogin: true }) .otherwise({ redirectTo: '/home' }) //$locationProvider.html5Mode(true).hashPrefix('!'); }]); app.config(['$httpProvider','adalAuthenticationServiceProvider',function ($httpProvider,adalAuthenticationServiceProvider) { // endpoint to resource mapping(optional) var endpoints = { "https://localhost/Api/": "xxx-bae6-4760-b434-xxx",}; adalAuthenticationServiceProvider.init( { // Config to specify endpoints and similar for your app clientId: "xxx-b7ab-4d1c-8cc8-xxx",// required //localLoginUrl: "/login",// optional //redirectUri : "your site",optional extraQueryParameter: 'domain_hint=mydomain.com',endpoints: endpoints // If you need to send CORS api requests. },$httpProvider // pass http provider to inject request interceptor to attach tokens ); }]); })();
呼叫端点的角码:
$scope.getItems = function () { $http.get("https://localhost/Api/Items") .then(function (response) { $scope.items = response.Items; });
解决方法
我有一个Angular SPA,通过Azure API管理(APIM)使用外部Web API.我的代码可能不是最佳实践,但它对我来说至今为止:)
SPA Azure AD应用程序具有访问外部API Azure AD应用程序的授权.
SPA(基于Adal TodoList SPA sample)
app.js
adalProvider.init( { instance: 'https://login.microsoftonline.com/',tenant: 'mysecrettenant.onmicrosoft.com',clientId: '********-****-****-****-**********',//ClientId of the Azure AD app for my SPA app extraQueryParameter: 'nux=1',cacheLocation: 'localStorage',// enable this for IE,as sessionStorage does not work for localhost. },$httpProvider );
来自todoListSvc.js的代码段
getWhoAmIBackend: function () { return $http.get('/api/Employee/GetWhoAmIBackend'); },
来自EmployeeController的代码段
public string GetWhoAmIBackend() { try { AuthenticationResult result = GetAuthenticated(); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",result.AccessToken); var request = new HttpRequestMessage() { RequestUri = new Uri(string.Format("{0}","https://api.mydomain.com/secretapi/api/Employees/GetWhoAmI")),Method = HttpMethod.Get,//This is the URL to my APIM endpoint,but you should be able to use a direct link to your external API }; request.Headers.Add("Ocp-Apim-Trace","true"); //Not needed if you don't use APIM request.Headers.Add("Ocp-Apim-Subscription-Key","******mysecret subscriptionkey****"); //Not needed if you don't use APIM var response = client.SendAsync(request).Result; if (response.IsSuccessStatusCode) { var res = response.Content.ReadAsStringAsync().Result; return res; } return "No dice :("; } catch (Exception e) { if (e.InnerException != null) throw e.InnerException; throw e; } } private static AuthenticationResult GetAuthenticated() { BootstrapContext bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; var token = bootstrapContext.Token; Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/mysecrettenant.onmicrosoft.com"); //The Client here is the SPA in Azure AD. The first param is the ClientId and the second is a key created in the Azure Portal for the AD App ClientCredential credential = new ClientCredential("clientid****-****","secretkey ********-****"); //Get username from Claims string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value; //Creating UserAssertion used for the "On-Behalf-Of" flow UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token,"urn:ietf:params:oauth:grant-type:jwt-bearer",userName); //Getting the token to talk to the external API var result = authContext.AcquireToken("https://mysecrettenant.onmicrosoft.com/backendAPI",credential,userAssertion); return result; }
现在,在我的后端外部API中,我的Startup.Auth.cs如下所示:
外部API
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app) { app.UseWindowsAzureActiveDirectoryBearerAuthentication( new WindowsAzureActiveDirectoryBearerAuthenticationOptions { Tenant = ConfigurationManager.AppSettings["ida:Tenant"],TokenValidationParameters = new TokenValidationParameters { ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],SaveSigninToken = true },AuthenticationType = "OAuth2Bearer" }); }
如果这有帮助,或者我可以进一步的帮助,请通知我.