【问题标题】:Azure authentication Audience validation failedAzure 身份验证受众验证失败
【发布时间】: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)
        {
        }
    }
}

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

【问题讨论】:

    标签: reactjs azure asp.net-core jwt msal


    【解决方案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 来执行此操作。

      【讨论】:

        猜你喜欢
        • 2021-11-28
        • 2020-08-06
        • 1970-01-01
        • 1970-01-01
        • 2020-07-24
        • 1970-01-01
        • 2022-11-04
        • 1970-01-01
        • 2017-05-13
        相关资源
        最近更新 更多