.net core 使用adaljs将请求从spa发送到.netcore web api始终返回401

.net core 使用adaljs将请求从spa发送到.netcore web api始终返回401,.net-core,asp.net-core-webapi,adal.js,.net Core,Asp.net Core Webapi,Adal.js,我有一个spa正在向web api发送get请求 当我没有authorize属性时,我可以获得值(当然!)。添加authorize属性总是返回401响应 在我连续两周对此感到困惑之后,我觉得只有上帝才能帮助我 我有以下问题: 我做错了什么 有更好的方法吗 如何在服务器端记录传入令牌? (这样我就可以在jwt.io上验证它) 假设我的钥匙、租户、客户(id)等设置正确 我的spa代码如下: 'use strict'; angular.module('todoApp') .controlle

我有一个spa正在向web api发送get请求

当我没有authorize属性时,我可以获得值(当然!)。添加authorize属性总是返回401响应

在我连续两周对此感到困惑之后,我觉得只有上帝才能帮助我

我有以下问题:

  • 我做错了什么
  • 有更好的方法吗
  • 如何在服务器端记录传入令牌? (这样我就可以在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');
                        });
                    });
    
            };
        }]);
    
     // 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();
    
            }
    
    我的Api Startup.cs如下所示:

    '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');
                        });
                    });
    
            };
        }]);
    
     // 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);
    }
    
    您正在使用resrouce
    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;
            });
        }
    }]);
    
    对于web API方面,我们需要指定
    权限
    受众
    或您想要的其他参数(请参阅第一段代码)

    在Azure方面,我们需要注册两个web应用程序。一个表示客户端,另一个表示受Azure AD保护的资源。例如,在我的测试场景中,我注册了ToOSPA和ToGoAPI并授予权限,如下图所示:

    为了使ToDoSPA应用程序与Azure AD集成,并为SPA应用程序提供隐式流,我们还需要修改其清单,将
    oauth2AllowImplicitFlow
    设置为
    true

    此外,以下是一些关于使用Azure AD保护web API的有用链接:

    更新(自定义AudienceValidator)
    //将应用程序配置为使用Jwt承载身份验证
    应用程序UseJWTBeareAuthentication(新JWTBeareOptions
    {
    TokenValidationParameters=新的Microsoft.IdentityModel.Tokens.TokenValidationParameters()
    {
    AudienceValidator=(受众、securityToken、validationParameters)=>
    {
    字符串[]AllowedAudits={”https://adfei.onmicrosoft.com/TodoListService", "https://graph.windows.net" };
    return allowedaccountries.Contains(accountries.First());
    },
    },
    自动验证=真,
    自动挑战=正确,
    Authority=String.Format(配置[“AzureAd:AadInstance”]、配置[“AzureAd:Tenant”]),
    //观众=配置[“AzureAd:观众”],
    });
    
    要从.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);
    }
    
    您正在使用resrouce
    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;
            });
        }
    }]);
    
    对于web API方面,我们需要指定
    权限
    受众
    或您想要的其他参数(请参阅第一段代码)

    在Azure方面,我们需要注册两个web应用程序。一个表示客户端,另一个表示受Azure AD保护的资源。例如,在我的测试场景中,我注册了ToOSPA和ToGoAPI并授予权限,如下图所示:

    为了使ToDoSPA应用程序与Azure AD集成,并为SPA应用程序提供隐式流,我们还需要修改其清单,将
    oauth2AllowImplicitFlow
    设置为
    true

    此外,以下是一些关于使用Azure AD保护web API的有用链接:

    更新(自定义AudienceValidator)
    //将应用程序配置为使用Jwt承载身份验证
    应用程序UseJWTBeareAuthentication(新JWTBeareOptions
    {
    TokenValidationParameters=新的Microsoft.IdentityModel.Tokens.TokenValidationParameters()
    {
    AudienceValidator=(受众、securityToken、validationParameters)=>
    {
    字符串[]AllowedAudits={”https://adfei.onmicrosoft.com/TodoListService", "https://graph.windows.net" };
    return allowedaccountries.Contains(accountries.First());
    },
    },
    自动验证=真,
    自动挑战=正确,
    Authority=String.Format(配置[“AzureAd:AadInstance”]、配置[“AzureAd:Tenant”]),
    //观众=配置[“AzureAd:观众”],
    });
    
    感谢您给出如此清晰简洁的回答。在做出建议的更改之后。我在调试控制台上没有看到任何错误(我以前看到过)。但是,在我的SPA上,我在控制台上收到此错误错误错误:AADSTS65001:用户或管理员未同意使用ID为“a77c3345-3962-40cf-8518-81baac0ace43”的应用程序。为此用户和资源发送交互式授权请求。跟踪ID:94c920d0-68e3-49aa-9a17-4d9d84314631相关ID:e6b165f4-b069-4815-905b-47a3ad9f8960时间戳:2016-12-29 16:04:23ZI如果您正在开发一个单一租户应用程序并通过Azure门户注册该应用程序,则无需向该应用程序提供许可,因为我们在注册时已经提供了许可。在另一种情况下,我们需要先授予应用程序权限。我们可以在获得管理员同意的情况下授予组织权限。发起广告