使用 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个星期,感觉只有大神能帮上忙

我有以下问题:

  1. 我做错了什么?
  2. 有更好的方法吗?
  3. 如何在服务器端记录传入的令牌? (这样我就可以在 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端,我们需要指定AuthorityAudience或其他您想要的参数(参考第一段代码)。

在 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"],              
});