在 ASP.NET Core 6 gRPC 服务中验证 AWS Cognito JWT
Validating AWS Cognito JWT in a ASP.NET Core 6 gRPC service
由于某种原因,AWS cognito 提供的 JWT (id) 令牌未在我的 gRPC 服务上通过令牌验证,我一直收到未经身份验证的响应。
这与默认的 JwtBearer 选项有关吗?
gRPC 服务Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenLocalhost(5000, o => o.Protocols = HttpProtocols.Http2);
});
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
options.MetadataAddress = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2/.well-known/openid-configuration";
});
builder.Services.AddAuthorization();
builder.Services.AddGrpc();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGrpcService<GreeterService>();
app.Run();
客户:
try
{
var greeterClient = new Greeter.GreeterClient(channel);
// id token
string id_token = "eyJraWQiOiJ4Qkk0MUNXYjdPUGtROGk2RWlhK1hQWlpjZ0ZcL0dOSFIwbFYyTTdLNVJhND0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI4M2FhY2U1Mi05NTJjLTQyMmEtODdkYy1iOWI1MDkyNGNmYjkiLCJjb2duaXRvOmdyb3VwcyI6WyJBZG1pbiJdLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfYlgxam5nN3EyIiwiY29nbml0bzp1c2VybmFtZSI6IjgzYWFjZTUyLTk1MmMtNDIyYS04N2RjLWI5YjUwOTI0Y2ZiOSIsIm9yaWdpbl9qdGkiOiI0MzQyZWJmNi0zYzQ4LTQ1YjEtODY1NC1kYTg1ZDQ1OGMyMDMiLCJhdWQiOiIyYzc0NGZoYmR1OTRpbm44dTRzdjRrZzBmdCIsImV2ZW50X2lkIjoiMDgyZDNlZDQtZTJlNy00OGRhLWJjOWYtZjgzMjY5NjAxZmQ0IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2NDg3MjgwMzgsImV4cCI6MTY0ODczMTYzOCwiaWF0IjoxNjQ4NzI4MDM4LCJqdGkiOiJkNzdlZTM3Yi1iMjA3LTRlZDctOTU3ZS1hY2E0OWE1ZmU0YWMiLCJlbWFpbCI6InNoYW1lZWwuZmF6dWxAbWFpbC5jb20ifQ.acGpo3owsd7gEvRtSTCijcRoIz4MP4MN8JUxBgM8mD8Oo-LBQam2uM2NxTtEygfx6MIWJMc9tNylv4GMm53bdrqBXCFeuYGiCdvdP4FvdFKkgwBV6Bzw7t0orN-P0zyrouDKW4NWIz2lUBvaOWE8j_fSdMhSsOlbbByDZH6mrNgugSWIXaF_frwIn2SjhMPnK4VO07uTdXMBiGvgkWH0JJidlU_vc9hjU33f";
// access token
string access_token = "eyJraWQiOiJPOWlVVWpWVjkrTTdZMXE4c0dieG9RWTNrUXB4S3oyNEZXbERiekN2Nm5zPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4M2FhY2U1Mi05NTJjLTQyMmEtODdkYy1iOWI1MDkyNGNmYjkiLCJjb2duaXRvOmdyb3VwcyI6WyJBZG1pbiJdLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9iWDFqbmc3cTIiLCJjbGllbnRfaWQiOiIyYzc0NGZoYmR1OTRpbm44dTRzdjRrZzBmdCIsIm9yaWdpbl9qdGkiOiIyNTBlNDAzMi1lNmE1LTQwMzYtOWMyMC1hZDhmNTBjYTk1YjUiLCJldmVudF9pZCI6ImE4YmZhYWJlLTBkMjYtNDYzNC04M2YwLWM2YTc5OWM4YTEzZiIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2NDkwNjMxOTAsImV4cCI6MTY0OTA2Njc5MCwiaWF0IjoxNjQ5MDYzMTkwLCJqdGkiOiI0YzhlYjg3OC04OTM0LTRkNTAtYWM2ZS0wZDZkYmYwNDY3YjYiLCJ1c2VybmFtZSI6IjgzYWFjZTUyLTk1MmMtNDIyYS04N2RjLWI5YjUwOTI0Y2ZiOSJ9.NTMlSa2xpQvMrmzqWYjK6449G9Hvp97JqhjsSE7dmNY5lo62XypyEpji6mCFCWlyD-b6om0mHmYNNknrG0UuD5dodMEI9AHK2u42jxzeQEndwkIEY827VUAOlHztdO3F4rsvT_P0TZmj4_3CvOladmd9KlW8ppWK5ZoFWUFniaFJOxUdfi6A-lBnJX2TxL1eEvLrLs6M5-HBOWLi8AekMsCc0aUrHPVzVTi9LUIjGXWmd6IkiG6HikC";
var headers = new Metadata();
headers.Add("Authorization", $"Bearer {access_token}");
var greeterResponse = await greeterClient.SayHelloAsync(new HelloRequest { Name = "John Doe" }, headers);
Console.WriteLine("Response Recieved: {0}", greeterResponse.Message);
}
catch (RpcException ex)
{
Console.WriteLine("{0} :: {1}", ex.StatusCode, ex.Message);
}
Exception: System.Exception: Status(StatusCode="Unauthenticated", Detail="Bad gRPC response. HTTP status code: 401")
---> Grpc.Core.RpcException: Status(StatusCode="Unauthenticated", Detail="Bad gRPC response. HTTP status code: 401")
Also, just so you know, this is just a test pool and the ids listed above are not sensitive as mentioned
通过搜索报错信息,很明显是token值有问题,导致验证失败
最重要的一点是,当我阅读 post 时,我忽略了您用来访问该服务的 ID 令牌。我熟悉 Azure AD,我认为您应该使用访问令牌 access.This 应该是解决此问题的关键。
使用TokenValidationParameters.IssuerSigningKeyResolver:
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = async (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
ConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(parameters.ValidIssuer + "/.well-known/jwks.json", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConnectConfiguration = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
return openIdConnectConfiguration.SigningKeys;
},
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
});
或原始方式:
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
// serialize the result
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
// cast the result to be the type expected by IssuerSigningKeyResolver
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
});
查看更多:
嗯...原来我用的是Microsoft.IdentityModel.Tokens包而不是System.IdentityModel.Tokens.Jwt
在 .NET 6 中 =>
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var keys = new HttpClient().GetFromJsonAsync<JsonWebKeySet>(parameters.ValidIssuer + "/.well-known/jwks.json");
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = "https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "{APP_CLIENT_ID}",
ValidateAudience = true
};
});
由于某种原因,AWS cognito 提供的 JWT (id) 令牌未在我的 gRPC 服务上通过令牌验证,我一直收到未经身份验证的响应。
这与默认的 JwtBearer 选项有关吗?
gRPC 服务Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenLocalhost(5000, o => o.Protocols = HttpProtocols.Http2);
});
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
options.MetadataAddress = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2/.well-known/openid-configuration";
});
builder.Services.AddAuthorization();
builder.Services.AddGrpc();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGrpcService<GreeterService>();
app.Run();
客户:
try
{
var greeterClient = new Greeter.GreeterClient(channel);
// id token
string id_token = "eyJraWQiOiJ4Qkk0MUNXYjdPUGtROGk2RWlhK1hQWlpjZ0ZcL0dOSFIwbFYyTTdLNVJhND0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI4M2FhY2U1Mi05NTJjLTQyMmEtODdkYy1iOWI1MDkyNGNmYjkiLCJjb2duaXRvOmdyb3VwcyI6WyJBZG1pbiJdLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfYlgxam5nN3EyIiwiY29nbml0bzp1c2VybmFtZSI6IjgzYWFjZTUyLTk1MmMtNDIyYS04N2RjLWI5YjUwOTI0Y2ZiOSIsIm9yaWdpbl9qdGkiOiI0MzQyZWJmNi0zYzQ4LTQ1YjEtODY1NC1kYTg1ZDQ1OGMyMDMiLCJhdWQiOiIyYzc0NGZoYmR1OTRpbm44dTRzdjRrZzBmdCIsImV2ZW50X2lkIjoiMDgyZDNlZDQtZTJlNy00OGRhLWJjOWYtZjgzMjY5NjAxZmQ0IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2NDg3MjgwMzgsImV4cCI6MTY0ODczMTYzOCwiaWF0IjoxNjQ4NzI4MDM4LCJqdGkiOiJkNzdlZTM3Yi1iMjA3LTRlZDctOTU3ZS1hY2E0OWE1ZmU0YWMiLCJlbWFpbCI6InNoYW1lZWwuZmF6dWxAbWFpbC5jb20ifQ.acGpo3owsd7gEvRtSTCijcRoIz4MP4MN8JUxBgM8mD8Oo-LBQam2uM2NxTtEygfx6MIWJMc9tNylv4GMm53bdrqBXCFeuYGiCdvdP4FvdFKkgwBV6Bzw7t0orN-P0zyrouDKW4NWIz2lUBvaOWE8j_fSdMhSsOlbbByDZH6mrNgugSWIXaF_frwIn2SjhMPnK4VO07uTdXMBiGvgkWH0JJidlU_vc9hjU33f";
// access token
string access_token = "eyJraWQiOiJPOWlVVWpWVjkrTTdZMXE4c0dieG9RWTNrUXB4S3oyNEZXbERiekN2Nm5zPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4M2FhY2U1Mi05NTJjLTQyMmEtODdkYy1iOWI1MDkyNGNmYjkiLCJjb2duaXRvOmdyb3VwcyI6WyJBZG1pbiJdLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9iWDFqbmc3cTIiLCJjbGllbnRfaWQiOiIyYzc0NGZoYmR1OTRpbm44dTRzdjRrZzBmdCIsIm9yaWdpbl9qdGkiOiIyNTBlNDAzMi1lNmE1LTQwMzYtOWMyMC1hZDhmNTBjYTk1YjUiLCJldmVudF9pZCI6ImE4YmZhYWJlLTBkMjYtNDYzNC04M2YwLWM2YTc5OWM4YTEzZiIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2NDkwNjMxOTAsImV4cCI6MTY0OTA2Njc5MCwiaWF0IjoxNjQ5MDYzMTkwLCJqdGkiOiI0YzhlYjg3OC04OTM0LTRkNTAtYWM2ZS0wZDZkYmYwNDY3YjYiLCJ1c2VybmFtZSI6IjgzYWFjZTUyLTk1MmMtNDIyYS04N2RjLWI5YjUwOTI0Y2ZiOSJ9.NTMlSa2xpQvMrmzqWYjK6449G9Hvp97JqhjsSE7dmNY5lo62XypyEpji6mCFCWlyD-b6om0mHmYNNknrG0UuD5dodMEI9AHK2u42jxzeQEndwkIEY827VUAOlHztdO3F4rsvT_P0TZmj4_3CvOladmd9KlW8ppWK5ZoFWUFniaFJOxUdfi6A-lBnJX2TxL1eEvLrLs6M5-HBOWLi8AekMsCc0aUrHPVzVTi9LUIjGXWmd6IkiG6HikC";
var headers = new Metadata();
headers.Add("Authorization", $"Bearer {access_token}");
var greeterResponse = await greeterClient.SayHelloAsync(new HelloRequest { Name = "John Doe" }, headers);
Console.WriteLine("Response Recieved: {0}", greeterResponse.Message);
}
catch (RpcException ex)
{
Console.WriteLine("{0} :: {1}", ex.StatusCode, ex.Message);
}
Exception: System.Exception: Status(StatusCode="Unauthenticated", Detail="Bad gRPC response. HTTP status code: 401") ---> Grpc.Core.RpcException: Status(StatusCode="Unauthenticated", Detail="Bad gRPC response. HTTP status code: 401")
Also, just so you know, this is just a test pool and the ids listed above are not sensitive as mentioned
通过搜索报错信息,很明显是token值有问题,导致验证失败
最重要的一点是,当我阅读 post 时,我忽略了您用来访问该服务的 ID 令牌。我熟悉 Azure AD,我认为您应该使用访问令牌 access.This 应该是解决此问题的关键。
使用TokenValidationParameters.IssuerSigningKeyResolver:
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = async (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
ConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(parameters.ValidIssuer + "/.well-known/jwks.json", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConnectConfiguration = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
return openIdConnectConfiguration.SigningKeys;
},
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
});
或原始方式:
builder.Services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// get JsonWebKeySet from AWS
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
// serialize the result
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
// cast the result to be the type expected by IssuerSigningKeyResolver
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_bX1jng7q2",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "2c744fhbdu94inn8u4sv4kg0ft",
ValidateAudience = true,
RoleClaimType = "cognito:groups"
};
});
查看更多:
嗯...原来我用的是Microsoft.IdentityModel.Tokens包而不是System.IdentityModel.Tokens.Jwt
在 .NET 6 中 =>
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var keys = new HttpClient().GetFromJsonAsync<JsonWebKeySet>(parameters.ValidIssuer + "/.well-known/jwks.json");
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = "https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}",
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidAudience = "{APP_CLIENT_ID}",
ValidateAudience = true
};
});