Azure 身份验证受众验证失败

Posted

技术标签:

【中文标题】Azure 身份验证受众验证失败【英文标题】:Azure authentication Audience validation failed 【发布时间】:2021-10-16 04:03:13 【问题描述】:

我已经构建了一个 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();
    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();
            );
        
    

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

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)
        
        
    

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

【问题讨论】:

【参考方案1】:

恐怕问题来自启动时的身份验证配置。请允许我显示我的代码 sn-p 以很好地解释它。

在我看来,您可以改用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" ;
        
    

【讨论】:

【参考方案2】:

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

【讨论】:

以上是关于Azure 身份验证受众验证失败的主要内容,如果未能解决你的问题,请参考以下文章

Azure AD B2C 受众验证失败

Azure AD 身份验证 401 错误“受众无效” AddAzureADBearer .Net Core Web Api

Microsoft Graph API 身份验证错误:“访问令牌验证失败。无效的受众”

如何查找 Active Directory OAuth 身份验证的受众字段? (如何从 Azure Logic App 向 DevOps 发送发布请求?)

Azure 应用服务中的 LDAP 身份验证失败

构建 VSTS 扩展 azure webapp 身份验证 JWT 令牌验证失败