使用 adal js 从 spa 向 .net core web api 发送请求总是 returns 401
Sending requests from spa to .netcore web api using adaljs always returns 401
我有一个正在向网络发送获取请求的水疗中心 api。
当我没有授权属性时,我可以获取值(当然!)。添加授权属性始终 returns 401 响应。
在这个问题上挠了挠头2个星期,感觉只有大神能帮上忙
我有以下问题:
- 我做错了什么?
- 有更好的方法吗?
- 如何在服务器端记录传入的令牌?
(这样我就可以在 jwt.io 验证它)
假设我的密钥、租户、客户端 (id) 等已正确设置,
我在spa上的代码是这样的:
'use strict';
angular.module('todoApp')
.controller('homeCtrl', ['$scope', '$http', 'adalAuthenticationService', '$location', function ($scope, $http, adalService, $location) {
$scope.apiData = [];
$scope.login = function () {
adalService.login().then(function () {
console.log('yay');
});
};
$scope.logout = function () {
adalService.logOut();
};
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
$scope.getData = function () {
// #1: Set up ADAL
var authContext = new AuthenticationContext({
clientId: 'myclientid',
postLogoutRedirectUri: window.location
});
var user = authContext.getCachedUser();
if (user) {
console.log(user);
console.log('Signed in as: ' + user.userName);
} else {
console.log('Not signed in');
}
var tokenStored;
authContext.acquireToken(
'https://graph.windows.net',
function (error, token) {
// TODO: Handle error obtaining access token
if (error || !token) {
console.log('Error no token');
return;
}
console.log("token is:" + token);
tokenStored = token;
$http.get('https://localhost:44301/api/values', {
headers: { 'Authorization': 'Bearer ' + tokenStored, }
}).then(function (response) {
$scope.apiData = response.data;
console.log(response);
alert('Data recieved');
});
});
};
}]);
我的 Api Startup.cs 看起来像这样:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//ToDo: Implement Logger Factory
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Shows UseCors with CorsPolicyBuilder.
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
// TokenValidationParameters = tokenValidationParameters
});
app.UseMvc();
}
我的控制器方法如下所示
[Route("api/values")]
[Authorize]
[EnableCors("CorsPolicy")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var results = _interconnectCodesRepository.GetCodes();
return Ok(results);
}
else
{
return BadRequest();
}
}
}
}
任何建议或提示将不胜感激。
谢谢
要从 .net core web API 项目获取令牌,我们可以添加 AuthenticationFailed
事件,如下所示:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
Events = new JwtBearerEvents
{
OnAuthenticationFailed= AuthenticationFailed
}
});
private Task AuthenticationFailed(AuthenticationFailedContext authenticationFailedContext)
{
Debug.WriteLine(authenticationFailedContext.Request.Headers["authorization"]);
return Task.FromResult(0);
}
您在代码中使用资源 https://graph.windows.net
获取的令牌是针对 Azure Graph REST 而不是您的 API。 SPA 应用程序无需在客户端手动获取令牌。 ADAL 库将根据资源自动获取和附加令牌。我们只需要初始化我们想要请求的端点。这是供您参考的js代码:
var myApp = angular.module('myApp', ['AdalAngular']).config(['$httpProvider', 'adalAuthenticationServiceProvider', function ($httpProvider, adalProvider) {
//{Array} endpoints - Collection of {Endpoint-ResourceId} used for automatically attaching tokens in webApi calls.
var endpoints = {
"https://localhost:44327/": "https://adfei.onmicrosoft.com/ToGoAPI",
};
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'adfei.onmicrosoft.com',
clientId: 'e2354bba-e915-4cb8-a48d-bcda101b8603',
extraQueryParameter: 'nux=1',
endpoints: endpoints,
},
$httpProvider
);
}])
myApp.controller('homeCtrl', ['$scope', '$http', 'adalAuthenticationService', '$location', 'toGoListSvc', function ($scope, $http, adalService, $location, toGoListSvc) {
$scope.double = function (value) { return value * 2; };
$scope.login = function () {
adalService.login();
};
$scope.logout = function () {
adalService.logOut();
};
$scope.getData = function () {
$http.defaults.useXDomain = true;
delete $http.defaults.headers.common['X-Requested-With'];
$http.get('https://localhost:44327/api/ToGoList').success(function (results) {
console.log(results)
$scope.toGoList = results;
});
}
}]);
对于网络API端,我们需要指定Authority
和Audience
或其他您想要的参数(参考第一段代码)。
在 Azure 端,我们需要注册两个 Web 应用程序。一个显示客户端,另一个显示受 Azure AD 保护的资源。例如,在我的测试场景中,我注册了 ToDoSPA 和 ToGoAPI 并授予了权限,如下图:
为了使 ToDoSPA 应用程序与 Azure AD 集成,并为 SPA 应用程序提供隐式流,我们还需要修改其清单以将 oauth2AllowImplicitFlow
设置为 true
。
此外,这里有一些关于使用 Azure AD 保护网络 API 的有用链接:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp-dotnet-webapi
https://github.com/AzureAD/azure-activedirectory-library-for-js
更新(自定义 AudienceValidator)
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters=new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
AudienceValidator = (audiences, securityToken, validationParameters) =>
{
string[] allowedAudiences = { "https://adfei.onmicrosoft.com/TodoListService", "https://graph.windows.net" };
return allowedAudiences.Contains<string>(audiences.First<string>());
},
},
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
//Audience = Configuration["AzureAd:Audience"],
});
我有一个正在向网络发送获取请求的水疗中心 api。
当我没有授权属性时,我可以获取值(当然!)。添加授权属性始终 returns 401 响应。
在这个问题上挠了挠头2个星期,感觉只有大神能帮上忙
我有以下问题:
- 我做错了什么?
- 有更好的方法吗?
- 如何在服务器端记录传入的令牌? (这样我就可以在 jwt.io 验证它)
假设我的密钥、租户、客户端 (id) 等已正确设置,
我在spa上的代码是这样的:
'use strict';
angular.module('todoApp')
.controller('homeCtrl', ['$scope', '$http', 'adalAuthenticationService', '$location', function ($scope, $http, adalService, $location) {
$scope.apiData = [];
$scope.login = function () {
adalService.login().then(function () {
console.log('yay');
});
};
$scope.logout = function () {
adalService.logOut();
};
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
$scope.getData = function () {
// #1: Set up ADAL
var authContext = new AuthenticationContext({
clientId: 'myclientid',
postLogoutRedirectUri: window.location
});
var user = authContext.getCachedUser();
if (user) {
console.log(user);
console.log('Signed in as: ' + user.userName);
} else {
console.log('Not signed in');
}
var tokenStored;
authContext.acquireToken(
'https://graph.windows.net',
function (error, token) {
// TODO: Handle error obtaining access token
if (error || !token) {
console.log('Error no token');
return;
}
console.log("token is:" + token);
tokenStored = token;
$http.get('https://localhost:44301/api/values', {
headers: { 'Authorization': 'Bearer ' + tokenStored, }
}).then(function (response) {
$scope.apiData = response.data;
console.log(response);
alert('Data recieved');
});
});
};
}]);
我的 Api Startup.cs 看起来像这样:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//ToDo: Implement Logger Factory
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Shows UseCors with CorsPolicyBuilder.
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
// TokenValidationParameters = tokenValidationParameters
});
app.UseMvc();
}
我的控制器方法如下所示
[Route("api/values")]
[Authorize]
[EnableCors("CorsPolicy")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var results = _interconnectCodesRepository.GetCodes();
return Ok(results);
}
else
{
return BadRequest();
}
}
}
}
任何建议或提示将不胜感激。
谢谢
要从 .net core web API 项目获取令牌,我们可以添加 AuthenticationFailed
事件,如下所示:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
Events = new JwtBearerEvents
{
OnAuthenticationFailed= AuthenticationFailed
}
});
private Task AuthenticationFailed(AuthenticationFailedContext authenticationFailedContext)
{
Debug.WriteLine(authenticationFailedContext.Request.Headers["authorization"]);
return Task.FromResult(0);
}
您在代码中使用资源 https://graph.windows.net
获取的令牌是针对 Azure Graph REST 而不是您的 API。 SPA 应用程序无需在客户端手动获取令牌。 ADAL 库将根据资源自动获取和附加令牌。我们只需要初始化我们想要请求的端点。这是供您参考的js代码:
var myApp = angular.module('myApp', ['AdalAngular']).config(['$httpProvider', 'adalAuthenticationServiceProvider', function ($httpProvider, adalProvider) {
//{Array} endpoints - Collection of {Endpoint-ResourceId} used for automatically attaching tokens in webApi calls.
var endpoints = {
"https://localhost:44327/": "https://adfei.onmicrosoft.com/ToGoAPI",
};
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'adfei.onmicrosoft.com',
clientId: 'e2354bba-e915-4cb8-a48d-bcda101b8603',
extraQueryParameter: 'nux=1',
endpoints: endpoints,
},
$httpProvider
);
}])
myApp.controller('homeCtrl', ['$scope', '$http', 'adalAuthenticationService', '$location', 'toGoListSvc', function ($scope, $http, adalService, $location, toGoListSvc) {
$scope.double = function (value) { return value * 2; };
$scope.login = function () {
adalService.login();
};
$scope.logout = function () {
adalService.logOut();
};
$scope.getData = function () {
$http.defaults.useXDomain = true;
delete $http.defaults.headers.common['X-Requested-With'];
$http.get('https://localhost:44327/api/ToGoList').success(function (results) {
console.log(results)
$scope.toGoList = results;
});
}
}]);
对于网络API端,我们需要指定Authority
和Audience
或其他您想要的参数(参考第一段代码)。
在 Azure 端,我们需要注册两个 Web 应用程序。一个显示客户端,另一个显示受 Azure AD 保护的资源。例如,在我的测试场景中,我注册了 ToDoSPA 和 ToGoAPI 并授予了权限,如下图:
为了使 ToDoSPA 应用程序与 Azure AD 集成,并为 SPA 应用程序提供隐式流,我们还需要修改其清单以将 oauth2AllowImplicitFlow
设置为 true
。
此外,这里有一些关于使用 Azure AD 保护网络 API 的有用链接:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp-dotnet-webapi
https://github.com/AzureAD/azure-activedirectory-library-for-js
更新(自定义 AudienceValidator)
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters=new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
AudienceValidator = (audiences, securityToken, validationParameters) =>
{
string[] allowedAudiences = { "https://adfei.onmicrosoft.com/TodoListService", "https://graph.windows.net" };
return allowedAudiences.Contains<string>(audiences.First<string>());
},
},
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
//Audience = Configuration["AzureAd:Audience"],
});