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" };
}
}
}
我构建了一个 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" };
}
}
}