问题是当我在启动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"
});
}
如果这有帮助,或者我可以进一步的帮助,请通知我.