繁体   English   中英

JQuery 到达.Net Web Api 使用 AAD 抛出 401 invalid_token / 发行者无效

[英]JQuery get to .Net Web Api using AAD throws 401 invalid_token / the issuer is invalid

我正在尝试在 Azure 中创建一个简单的.Net Core Web Api 以使用 Z6A580F142203677F1F0BC30898F63F53Z 测试身份验证。 我设法解决了 CORS 问题,但在尝试使用不记名令牌时,我不断收到 401“发行者无效”错误。 我能够使用 Postman 和秘密测试 Web Api 和一个秘密,但在使用 Z6A2D7208853DAFFCB96B 我从一个单独工作的示例中提取了一些演示 SPA 代码,但当我将客户端和 Web Api 保留在单独的项目中时,我没有。 我想我可能需要使用 Web Api 的客户端 ID 来获取我的令牌,但这似乎没有任何效果。 controller 再简单不过了。

namespace test_core_web_api_spa.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody] string value)
        {
            // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
            // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
            // For more information on protecting this API from Cross Site Request Forgery (CSRF) attacks, see https://go.microsoft.com/fwlink/?LinkID=717803
        }
    }
}

Web Api 的启动是基本的。

namespace test_core_web_api_spa
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
            });
            services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
                .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseCors(options => options.WithOrigins("https://localhost:44399", "https://localhost:44308").AllowAnyMethod().AllowAnyHeader());
            app.UseMvc();
        }
    }
}

HTML 页面是从使用 AAD 的 SPA 演示中复制的。

<!DOCTYPE html>
<html>
<head>
    <title>Test API Call</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="/css/app.css">
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed"
                        data-toggle="collapse"
                        data-target=".navbar-collapse">

                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/#Home">test api call</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a href="/#Home">Home</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">
                    <li class="app-user navbar-text"></li>
                    <li><a href="javascript:;" class="app-logout">Logout</a></li>
                    <li><a href="javascript:;" class="app-login">Login</a></li>
                </ul>
            </div>
        </div>
    </div>

    <div id="divHome" class="container-fluid">
        <div class="jumbotron">
            <h5 id="WelcomeMessage"></h5>
            <div class="text-hide">Surname: <span id="userSurName"></span><span id="userEmail"></span></div>
            <h2>test page</h2>
        </div>

        <div>
            <br />
            <a href="javascript:;" class="btnCallApiTest">Call Api</a>
            <br />
            <p class="view-loading">Loading...</p>
            <div class="app-error"></div>
            <br />
            <span id="data-container"></span>
        </div>
        <br />
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.1.3/js/msal.js" integrity="sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ" crossorigin="anonymous"></script>
    <script src="js/rest_api.js"></script>

</body>
</html>

并且 Javascript 也是从 GitHub 中的 SPA AAD 演示中借来的。

// the AAD application
var clientApplication;

(function () {
    console.log("document ready done");
    window.config = {
        clientID: 'clientidof_web_api_in_azure'
    };

    const loginRequest = {
        scopes: ["openid", "profile", "User.Read"]
    };
    const tokenRequest2 = {
        scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa"]
    };
    var scope = [window.config.clientID];
    const msalConfigDemo = {
            auth: {
                clientId: "myclientid", 
                authority: "https://login.microsoftonline.com/mytenantid",
                consentScopes: ["user.read","https://myportal.onmicrosoft.com/test_core_web_api_spa/user_impersonation"],
                validateAuthority: true
            },
            cache: {
                cacheLocation: "localStorage",
                storeAuthStateInCookie: false
            }
    };
    function authCallback(errorDesc, token, error, tokenType) {
        //This function is called after loginRedirect and acquireTokenRedirect. Not called with loginPopup
        // msal object is bound to the window object after the constructor is called.
        if (token) {
            log("authCallback success");
            console.log({ 'token': token });
            console.log({ 'tokenType': tokenType });
        }
        else {
            log(error + ":" + errorDesc);
        }
    }

    if (!clientApplication) {
        clientApplication = new clientApplication = new Msal.UserAgentApplication(msalConfigDemo, msalConfigDemo, authCallback);
    } else {
        console.log({ 'clientApplication': clientApplication });
    }

    // Get UI jQuery Objects
    var $panel = $(".panel-body");
    var $userDisplay = $(".app-user");
    var $signInButton = $(".app-login");
    var $signOutButton = $(".app-logout");
    var $errorMessage = $(".app-error");
    var $btnCallApiTest = $(".btnCallApiTest");
    onSignin(null);


    // Handle Navigation Directly to View
    window.onhashchange = function () {
        loadView(stripHash(window.location.hash));
    };
    window.onload = function () {
        $(window).trigger("hashchange");
    };

    $btnCallApiTest.click(function () {
        call_api_test();
    });

    // Register NavBar Click Handlers
    $signOutButton.click(function () {
        clientApplication.logout();
    });

    $signInButton.click(function () {
        clientApplication.loginPopup(loginRequest).then(onSignin);
    });


    function stripHash(view) {
        return view.substr(view.indexOf('#') + 1);
    }

    function call_api_test() {
        // Empty Old View Contents
        var $dataContainer = $(".data-container");
        $dataContainer.empty();
        var $loading = $(".view-loading");

        clientApplication.acquireTokenSilent(tokenRequest2)
            .then(function (token) {
                getTodoList(token.accessToken, $dataContainer, $loading);
            }, function (error) {
                clientApplication.acquireTokenPopup(tokenRequest2).then(function (token) {
                    getTodoList(token.accessToken, $dataContainer, $loading);
                }, function (error) {
                    printErrorMessage(error);
                });
            });
    }

    function getTodoList(accessToken, dataContainer, loading) {
        // Get TodoList Data
        let urlstring = 'https://localhost:44363/api/values';
        console.log({ 'accessToken': accessToken });
        $.ajax({
            type: "GET",
            url: urlstring,
            headers: {
                'Authorization': 'Bearer ' + accessToken,
            },
        }).done(function (data) {
            // Update the UI
            console.log({ 'data': data });
            loading.hide();
            dataContainer.html(data);
        }).fail(function (jqXHR, textStatus) {
            printErrorMessage('Error getting todo list data statusText->' + textStatus + ' status->' + jqXHR.status);
            console.log({ 'jqXHR': jqXHR });
            loading.hide();
        }).always(function () {
            // Register Handlers for Buttons in Data Table
            //registerDataClickHandlers();
        });
    }

    function printErrorMessage(mes) {
        var $errorMessage = $(".app-error");
        $errorMessage.html(mes);
    }
    function onSignin(idToken) {
        // Check Login Status, Update UI
        var user = clientApplication.getUser();
        if (user) {
            $userDisplay.html(user.name);
            $userDisplay.show();
            $signInButton.hide();
            $signOutButton.show();
        } else {
            $userDisplay.empty();
            $userDisplay.hide();
            $signInButton.show();
            $signOutButton.hide();
        }

    }
}());

登录确实可以使用 AAD 并拉回我的 email 地址。 我确实看到创建了一个令牌并将其传递给 Web Api。 但随后它给出了“发行者无效”401错误。 在发出令牌请求时,我是 Web Api 的客户端 ID,所以我不确定还有什么可以更改的。

根据评论,我尝试将 scope 传递给loginPopup调用。

    $signInButton.click(function () {
        clientApplication.loginPopup(requestObj).then(onSignin);
    });

然而,唯一有效的值给出了相同的结果:

var requestObj = ["web-api-client-id"];

I've tried the URL of the local web service running including combinations using https://localhost:44399/.default but that throws immediate errors before getting a token with a message like the resource principal named https://localhost:44399 was not found in the tenant 如果问题是此调用中的 scope 设置,那么我不确定在调试时使用什么值来使其在本地工作。 作为旁注,我发现其他 Github 示例使用以下格式

var requestObj = {scopes: ["api://clientid/access_as_user"]};

但这些无法执行说API does not accept non-array scopes 我可能会在一个单独的线程中问这个问题。

11 月 13 日更新我从 MSAL 的 0.2.3 切换到 1.1.3,然后更新了逻辑以反映不同版本中所做的更改。

我还确认客户端应用程序对 web api 具有 API 权限。 我在 web api 中添加了一个新的 scope,称为“user_impersonation”。 现有的“api-access”在我的租户中被锁定为管理员控制。

当尝试使用“api//”形式时,它找不到资源。 这是我尝试过的值,它们都得到相同的错误。 我认为这种格式是遗留的。


scopes: ["api://web-api-clientid"]
scopes: ["api://web-api-clientid/api-access"]
scopes: ["api://web-api-clientid/user_impersonation"]
scopes: ["api://web-api-clientid/.default"]
ServerError: AADSTS500011: The resource principal named api://web-api-clientid was not found in the tenant named my-tenant-id. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.

尝试这些范围时,错误是 401 受众无效。

scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa/user_impersonation"]
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa/.default"]
401 www-authenticate: Bearer error="invalid_token", error_description="The audience is invalid" 
"aud": "https://myPortal.onmicrosoft.com/test_core_web_api_spa"

尝试这些范围时,错误消息使我的客户端应用程序正确,但似乎再次认为我的 web api 应用程序存在于我的门户上。

scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa"]
scopes: ["https://myPortal.onmicrosoft.com/test_core_web_api_spa","user_impersonation"]
ServerError: AADSTS650053: The application 'demoapp-frontend' asked for scope 'test_core_web_api_spa' that doesn't exist on the resource 'myportal_guid'. 

抱歉所有的混乱,但我一直在尝试一切,代码变得混乱。 我几乎到了可能需要重新开始的地步。

修改您的代码如下

window.config = {
        clientID: 'clientidof_web_client'
    };
    var scope = ["api://{clientidof_web_api}/.default"];

您可以通过解码来检查令牌。 aud 的值应该是api://client_id_of_web_api

在此处输入图像描述

更新:

您是否已将 api 添加到您的客户端应用程序权限?

在此处输入图像描述

问题在于 Web API 的配置数据。 当他们说ClientId时,他们真正想要的是“公开 API”选项下的值,其中显示“应用程序 ID URI”。 我放在那里的是 Web Api 应用程序注册的指南。 下面是它的外观。

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "myportal.onmicrosoft.com",
    "TenantId": "mytenant-guid",
    "ClientId": "https://myportal.onmicrosoft.com/test_core_web_api_spa"
  },

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM