Azure 身份验证受众验证失败

Azure authentication Audience validation failed

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

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

我通过 MSAL 客户端方法 acquireTokenSilent 请求了一个令牌,其权限范围在 Azure 上建立。我都试过了,我已经在客户端和 Web 中更改了 ClientId 和 ResourceId 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

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

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();
            });
        }
    }
}

这是我的初创公司class,我在其中配置身份验证中间件

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)
        {
        }
    }
}

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

错误消息指出身份验证中间件无法成功验证请求,因为令牌中的受众不是有效受众的一部分。 要解决此问题,您可以在您的配置中提及哪些是有效受众,并且您的令牌应具有该受众。要检查令牌中的受众,您可以在 jwt.io 中查看令牌字段 此外,如果您想跳过受众验证,可以在配置身份验证中间件时通过将 ValidateAudience 标记为 false 来实现。

恐怕问题来自启动时的身份验证配置。请允许我展示我的代码片段来很好地解释它。

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

暴露的步骤api,大家可以按照文档来做。我想在这里重复的是当你生成一个访问令牌时,它应该有像 api://clientid_of_the_app_exposed_api/tiny/User.Read 这样的范围,它可以匹配 appsettings.json

中的配置

我的react代码,参考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);
                });
            }
        

启动文件中我的ConfigureServices,这些参考this document:

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

我的应用程序设置:

{
  "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"
  }
}

我的控制器:

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" };
        }
    }
}