Azure 身份验证受众验证失败 [英] Azure authentication Audience validation failed

查看:34
本文介绍了Azure 身份验证受众验证失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经构建了一个 ASP.net 核心单租户 Web API,它需要来自 Azure 的令牌,我还通过 react 构建了一个单租户 SPA,它使用 Azure 通过 MSAL-Brower 登录.我想在登录时使用 azure 提供的令牌来验证我的客户端 SPA 以调用我的 API.令牌请求成功返回,但是当我去获取时,我的 api 上收到一个错误,指出

<块引用>

不匹配:validationParameters.ValidAudience: 'System.String' 或 validationParameters.ValidAudiences: 'System.String'.

我通过 MSAL 客户端方法 acquireTokenSilent 请求了一个令牌,该令牌具有在 Azure 上建立的权限范围.我已经尝试了所有方法,我在客户端和 Web API 中都更改了 ClientId 和 ResourceId.

const PostToDataBase = () =>{const authenticationModule: AzureAuthenticationContext = new AzureAuthenticationContext();常量帐户 = authenticationModule.myMSALObj.getAllAccounts()[0]常量端点 = {端点:https://localhost:44309/api/values",scopes:[],//为 SO 编辑资源ID:"//为 SO 编辑}异步函数 postValues(值:字符串){如果(值.长度 < 1){console.log("值不能为空!")返回;}控制台日志(帐户)如果(帐户){console.log("获取令牌")authenticationModule.myMSALObj.acquireTokenSilent({范围:endpoint.scopes,帐户:帐户}).then(响应 => {console.log("获取令牌到服务器")常量标头 = 新标头();const bearer = `Bearer ${response.accessToken}`;headers.append(授权",承载);headers.append("Content-Type", "'application/json'")常量选项 = {方法:POST",标题:标题,bodyInit:JSON.stringify(值)};返回获取(endpoint.endpoint,选项).then(response => console.log(response)).catch(error => console.log(error));}).catch(错误 => {控制台错误(错误)if(err instanceof InteractionRequiredAuthError){如果(帐户){authenticationModule.myMSALObj.acquireTokenPopup({范围:endpoint.scopes}).then(响应 => {常量标头 = 新标头();const bearer = `Bearer ${response.accessToken}`;headers.append(授权",承载);常量选项 = {方法:POST",标题:标题,身体:价值};返回获取(endpoint.endpoint,选项).then(response => response.json()).catch(error => console.log(error));}).catch(err => console.error(err))}}})}}异步函数 getValues(){控制台日志(帐户)如果(帐户){console.log("获取令牌")authenticationModule.myMSALObj.acquireTokenSilent({范围:endpoint.scopes,帐户:帐户}).then(响应 => {console.log("获取令牌到服务器")常量标头 = 新标头();const bearer = `Bearer ${response.accessToken}`;headers.append(授权",承载);headers.append("Content-Type", "'application/json'")常量选项 = {方法:获取",标头:标头};返回获取(endpoint.endpoint,选项).then(response => response.json()).then(res => setValues(res)).catch(error => console.log(error));}).catch(错误 => {控制台错误(错误)if(err instanceof InteractionRequiredAuthError){如果(帐户){authenticationModule.myMSALObj.acquireTokenPopup({范围:endpoint.scopes}).then(响应 => {常量标头 = 新标头();const bearer = `Bearer ${response.accessToken}`;headers.append(授权",承载);常量选项 = {方法:获取",标题:标题,};返回获取(endpoint.endpoint,选项).then(response => response.json()).then(res => setValues(res)).catch(error => console.log(error));}).catch(err => console.error(err))}}})}}常量 [值,setValues] = useState([]);const [inputValue, setInput] = useState("");使用效果(() => {//异步函数 getinit(){//const values = await fetch("https://localhost:44309/api/values")//.then(res => res.json())//.catch(e =>//console.error(e))//setValues(值)//console.log(值)//}获取值()},[获取值])返回 (

{值 === 未定义?<p>没有要显示的值</p>:values.map((n,i)=>(<p key={i}>{n}</p>))}<表格><输入名称=输入值";值={inputValue} onChange={(e)=>setInput(e.target.value)} 需要></input></表格><按钮点击={() =>postValues(inputValue)}>发布到服务器</button></div>)}导出默认 PostToDataBase

这是一个调用api的功能组件,这个页面只有在用户登录后才能访问.

使用系统;使用 System.Collections.Generic;使用 System.Linq;使用 System.Threading.Tasks;使用 Microsoft.AspNetCore.Builder;使用 Microsoft.AspNetCore.Hosting;使用 Microsoft.AspNetCore.HttpsPolicy;使用 Microsoft.AspNetCore.Mvc;使用 Microsoft.Extensions.Configuration;使用 Microsoft.Extensions.DependencyInjection;使用 Microsoft.Extensions.Logging;使用 Microsoft.Extensions.Options;使用 Microsoft.Extensions.Hosting;使用 Microsoft.AspNetCore.Authentication.JwtBearer;命名空间 McQuillingWebAPI{公共类启动{公共启动(IConfiguration 配置){配置=配置;}公共 IConfiguration 配置 { 获取;}//这个方法被运行时调用.使用此方法向容器添加服务.公共无效配置服务(IServiceCollection 服务){//在生产环境中更改为客户端 urlservices.AddCors(o => o.AddPolicy("MyPolicy", builder =>{builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();}));services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt =>{opt.Audience = 配置[AAD:ResourceId"];opt.Authority = $"{Configuration["AAD:Instance"]}{Configuration["AAD:TenantId"]}";});服务.AddControllers();}//这个方法被运行时调用.使用此方法配置 HTTP 请求管道.公共无效配置(IApplicationBuilder 应用程序,IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCors("MyPolicy");app.UseHttpsRedirection();app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(端点 =>{端点.MapControllers();});}}}

这是我为身份验证配置中间件的启动类

使用系统;使用 System.Collections.Generic;使用 System.Linq;使用 System.Threading.Tasks;使用 Microsoft.AspNetCore.Mvc;使用 Microsoft.AspNetCore.Authorization;使用 Microsoft.Identity.Web.Resource;命名空间 McQuillingWebAPI.Controllers{[授权][路由(api/[控制器]")][API控制器]公共类 ValuesController : ControllerBase{//获取 api/值[HttpGet][RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]public ActionResult<IEnumerable<string>>得到(){return new string[] { "value1", "value2";};}//获取 api/values/5[HttpGet("{id}")]公共 ActionResult<字符串>获取(int id){返回值";}//POST api/值[授权][HttpPost]公共 IActionResult Post([FromBody] 字符串值){返回确定(已发布");}//输入 api/values/5[HttpPut("{id}")]public void Put(int id, [FromBody] 字符串值){}//删除 api/values/5[HttpDelete("{id}")]公共无效删除(int id){}}}

这是我正在测试身份验证的生成控制器之一

解决方案

恐怕问题出在启动时的 auth 配置上.请允许我展示我的代码片段以很好地解释它.

在我看来,您可以改用 services.AddMicrosoftIdentityWebApiAuthentication(Configuration);.并且你应该正确地暴露 api.

公开api的步骤,可以按照文档进行.我想在这里重复的是,当您生成访问令牌时,它应该具有像 api://clientid_of_the_app_exposed_api/tiny/User.Read 这样的范围,可以匹配 appsettings.json

我的 react 代码,参考

I've built an ASP.net core Single tenant Web API that requires a token from Azure, I have also built a single tenant SPA via react that uses Azure to login Via MSAL-Brower. I want to use the token provided from azure when I log in to authenticate my client SPA to call my API. The token request comes back successfully but when I go to fetch I receive an error on my api stating that

Did not match: validationParameters.ValidAudience: 'System.String' or validationParameters.ValidAudiences: 'System.String'.

I requested a token via MSAL Client method acquireTokenSilent with the scope of permissions established on Azure. Ive tried it all, Ive changed the ClientId and ResourceId in both the Client and the Web API.

const PostToDataBase = () => {
    const authenticationModule: AzureAuthenticationContext = new AzureAuthenticationContext();
    const account = authenticationModule.myMSALObj.getAllAccounts()[0]
    const endpoint = {
        endpoint:"https://localhost:44309/api/values",
        scopes:[], // redacted for SO
        resourceId : "" // redacted for SO
    }

    async function postValues(value:string){
        if(value.length < 1){
            console.log("Value can not be null!")
            return;
        }
        console.log(account)
        if(account ){
            console.log("acquiring token")
            authenticationModule.myMSALObj.acquireTokenSilent({
                scopes: endpoint.scopes,
                account: account
            }).then(response => {
                console.log("token acquired posting to server")
                const headers = new Headers();
                const bearer = `Bearer ${response.accessToken}`;
                headers.append("Authorization", bearer);
                headers.append("Content-Type", "'application/json'")
                const options = {
                    method: "POST",
                    headers: headers,
                    bodyInit: JSON.stringify(value)
                };
                return fetch(endpoint.endpoint, options)
                .then(response => console.log(response))
                .catch(error => console.log(error));
            }).catch(err => {
                console.error(err)
                if(err instanceof InteractionRequiredAuthError){
                    if(account ){
                        authenticationModule.myMSALObj.acquireTokenPopup({
                            scopes: endpoint.scopes
                        }).then(response => {
                            const headers = new Headers();
                            const bearer = `Bearer ${response.accessToken}`;
                            headers.append("Authorization", bearer);

                            const options = {
                                method: "POST",
                                headers: headers,
                                body: value
                            };
                            return fetch(endpoint.endpoint, options)
                            .then(response => response.json())
                            .catch(error => console.log(error));
                        }).catch(err => console.error(err))
                    }
                }
            })
        }
        
    }
    async function getValues(){
        
        console.log(account)
        if(account ){
            console.log("acquiring token")
            authenticationModule.myMSALObj.acquireTokenSilent({
                scopes: endpoint.scopes,
                account: account
            }).then(response => {
                console.log("token acquired posting to server")
                const headers = new Headers();
                const bearer = `Bearer ${response.accessToken}`;
                headers.append("Authorization", bearer);
                headers.append("Content-Type", "'application/json'")
                const options = {
                    method: "GET",
                    headers: headers
                };
                return fetch(endpoint.endpoint, options)
                .then(response => response.json())
                .then(res => setValues(res))
                .catch(error => console.log(error));
            }).catch(err => {
                console.error(err)
                if(err instanceof InteractionRequiredAuthError){
                    if(account ){
                        authenticationModule.myMSALObj.acquireTokenPopup({
                            scopes: endpoint.scopes
                        }).then(response => {
                            const headers = new Headers();
                            const bearer = `Bearer ${response.accessToken}`;
                            headers.append("Authorization", bearer);

                            const options = {
                                method: "GET",
                                headers: headers,
                            };
                            return fetch(endpoint.endpoint, options)
                            .then(response => response.json())
                            .then(res => setValues(res))
                            .catch(error => console.log(error));
                        }).catch(err => console.error(err))
                    }
                }
            })
        }
        
    }

    const [values, setValues] = useState([]);
    const [inputValue, setInput] = useState("");
    useEffect(() => {
        // async function getinit(){
        //     const values = await fetch("https://localhost:44309/api/values")
        //     .then(res => res.json())
        //     .catch(e =>
        //         console.error(e))
        //     setValues(values)
        //     console.log(values)
        // } 

        getValues()
    }, [ getValues])
    return (
        <div>
            {values === undefined ? <p>no values to show</p> :
            values.map((n,i)=>( <p key={i}>{n}</p>))}
            <form>
                <input name="inputValues" value={inputValue} onChange={(e)=> setInput(e.target.value)} required></input>
            </form>
            <button onClick={() => postValues(inputValue)}>Post to Server</button>
        </div>
    )
}

export default PostToDataBase

This is functional component that makes a call to the api, this pages is only accessible after the user logs in.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace McQuillingWebAPI
{
    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)
        {
            //change to client url in production 
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(opt =>
                {
                    opt.Audience = Configuration["AAD:ResourceId"];
                    opt.Authority = $"{Configuration["AAD:Instance"]}{Configuration["AAD:TenantId"]}";
                });

            services.AddControllers();
        }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("MyPolicy");
            app.UseHttpsRedirection();

            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

This is my startup class where I configure middleware for Authentication

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

using Microsoft.Identity.Web.Resource;

namespace McQuillingWebAPI.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
 
        
        [HttpGet]
        [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
        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
        [Authorize]
        [HttpPost]
        public IActionResult Post([FromBody] string value)
        {
            return Ok("Posted");
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

this is one of the generated controllers that I'm testing authentication with

解决方案

I'm afraid the issue comes from the auth configuration in startup. Pls allow me show my code snippet to explain it well.

In my opinion, you could use services.AddMicrosoftIdentityWebApiAuthentication(Configuration); instead. And you should exposed the api correctly.

The steps of exposing api, you can follow the documents. What I wanna repeat here is when you generate an access token, it should have the scope like api://clientid_of_the_app_exposed_api/tiny/User.Read which can match the configuration in appsettings.json

My react code, it is referred to this sample:

import { AuthenticatedTemplate, UnauthenticatedTemplate, useMsal } from "@azure/msal-react";  
const callApi = (accessToken) => {
            const headers = new Headers();
            const bearer = `Bearer ${accessToken}`;
    
            headers.append("Authorization", bearer);
    
            const options = {
                method: "GET",
                headers: headers
            };
    
            fetch("https://localhost:44341/api/home", options)
                .then(response => {
                    var a = response.json();
                    console.log(a);
                })
                .catch(error => console.log(error));
        };
    
        const ProfileContent = () => {
            const { instance , accounts} = useMsal();
            const [graphData, setGraphData] = useState(null);
            const loginRequest = {"scopes": ["api://clientid_of_the_app_exposed_api/tiny/User.Read"]};
        
            function RequestProfileData() {
                instance.acquireTokenSilent({
                    ...loginRequest,
                    account: accounts[0]
                }).then((response) => {
                    callApi(response.accessToken);
                });
            }
        

My ConfigureServices in startup file, these are referred to this document:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
            services.AddControllers();
        }

My appsettings :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "clientid_which_have_api_permission",
    "Domain": "tenantname.onmicrosoft.com",
    "TenantId": "common",
    "Audience": "clientid_of_the_app_exposed_api"
  }
}

My controller:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;

namespace WebApplication1.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [RequiredScope("User.Read")]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }
}

这篇关于Azure 身份验证受众验证失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆