JQuery попадает в .Net Web Api, используя AAD, выбрасывает 401 invalid_token / издатель недействителен - PullRequest
0 голосов
/ 08 ноября 2019

Я пытаюсь создать простой .Net Core Web Api в Azure для проверки подлинности с использованием JQuery. Мне удалось решить проблему с CORS, но я продолжаю выдавать ошибку 401 «издатель недействителен» при попытке использовать токен канала-носителя. Я смог протестировать Web Api, используя Postman и секрет, но не при использовании JQuery и AAD. Я взял некоторый демонстрационный SPA-код из примера, который работает отдельно, но не тогда, когда я храню клиент и Web Api в отдельных проектах. Я подумал, может быть, мне нужно было использовать идентификатор клиента Web Api, чтобы получить мой токен, но это, похоже, не имеет никакого эффекта. Контроллер не может быть более простым.

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-страница была скопирована из демонстрации SPA с использованием AAD.

<!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 также был заимствован из демонстрации SPA AAD в GitHub.

// 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 и возвращает мой адрес электронной почты. Я вижу, что токен создан и передан в Web Api. Но тогда он выдаёт ошибку «эмитент неверен» 401. Я являюсь идентификатором клиента Web Api при выполнении запроса токена, поэтому я не уверен, что еще можно изменить.

В комментариях я пытался передать область действия на вызов loginPopup.

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

Однако единственное работающее значение дает те же результаты:

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

Я пробовал URL-адрес запущенного локального веб-сервиса, включая комбинации, использующие https://localhost:44399/.default, но это выдаетнемедленные ошибки до получения токена с сообщением типа the resource principal named https://localhost:44399 was not found in the tenant. Если проблема заключается в настройке области действия в этом вызове, то я не уверен, какое значение использовать, чтобы это работало локально при отладке. В качестве примечания я обнаружил другие образцы Github, использующие формат

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

, но они не выполняются, говоря API does not accept non-array scopes. Я мог бы спросить об этом в отдельном потоке.

Обновление 13 ноября Я переключился с 0.2.3 MSAL на 1.1.3 и затем обновил логику, чтобы отразить изменения, сделанные в разных версиях,

Я также подтвердил, что клиентское приложение имеет разрешения API для веб-API. Я добавил новую область в веб-API под названием «user_impersonation». Существующий «api-доступ» был заблокирован для административного контроля в моем клиенте.

При попытке использовать форму «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"

При попытке этих областей сообщение об ошибке получает правильное мое клиентское приложение, ноопять же не похоже, что мое веб-приложение 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'. 

Извините за путаницу, но я пробовал все, и код запутался. Я почти до такой степени, что мне, возможно, придется начать все сначала.

1 Ответ

0 голосов
/ 08 ноября 2019

Измените ваш код, как показано ниже

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

Вы можете проверить токен с помощью декодирования it. Значение aud должно быть api://client_id_of_web_api

enter image description here

Обновление:

Вы добавили свой API вразрешение вашего клиентского приложения?

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...