在 Asp Net Core 3.1 中手动验证来自 OIDC 提供商的令牌,而没有“众所周知”的元数据
Manually validating a token from an OIDC provider without `well-known` metadata in Asp Net Core 3.1
我正在使用带有 Openid 的流程,我将我的用户重定向到另一个提供商,以处理登录问题,并且在这次登录之后,我在我的 /login/callback? 代码中收到了 code
=xxxx URL.
因此,JWT 是使用 code
生成的,但我无法验证它。我的 STS 中没有 well-known
端点,我需要以这种方式手动配置:
services.AddAuthorization(cfg =>
{
cfg.AddPolicy("MyPolicy", cfgPolicy =>
{
cfgPolicy.AddRequirements().RequireAuthenticatedUser();
cfgPolicy.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme);
});
}).AddAuthentication(cfg =>
{
cfg.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(cfg =>
{
cfg.ClientId = authenticationConfig.ClientId;
cfg.ClientSecret = authenticationConfig.ClientSecret;
cfg.ResponseType = "code";
cfg.CallbackPath = "/login/callback";
cfg.Scope.Clear();
cfg.Scope.Add("openid");
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://myissuer"
};
cfg.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://mysts/api/oauth/authorize",
TokenEndpoint = "https://mysts/api/oauth/token",
UserInfoEndpoint = "https://mysts/api/oauth/token_info"
};
});
一些要点:
- 我有一个令牌内省端点,用于验证我的令牌(token_info 端点)。
- 我没有 return public 键 (jwks) 的默认端点。我的端点始终是来自某些值的连接,类似 --> https://mysts/offline/jwks/{kid}/{clientid},所以这是动态的并且取决于令牌。
- 我没有众所周知的端点。
您无法验证它,因为 ASP.NET Core 默认会尝试验证 JWT 签名。但是,由于您自己设置了元数据,并且没有给出 JwksUri
,它无法获取 OIDC 提供商的 public 密钥来验证签名。
您可以指定JwksUri
,或关闭签名验证(不安全),或自行验证。如果您有权访问 public 密钥,请使用 options.SecurityTokenValidator
并实施 ISecurityTokenValidator
进行自定义验证。
这是一个动态获取 JWKS 并验证令牌的(未经测试的)实现:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddOpenIdConnect(options => {
options.SecurityTokenValidator = new MyJwtValidator(Configuration);
});
}
class MyJwtValidator: ISecurityTokenValidator
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
private readonly JwtSecurityTokenHandler _tokenHandler;
public MyJwtValidator(IConfiguration configuration)
{
_configuration = configuration;
_tokenHandler = new JwtSecurityTokenHandler();
_httpClient = new HttpClient
{
BaseAddress = new Uri(_configuration.GetSection("OidcProvider").Get<string>())
};
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(
string securityToken,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken
)
{
// parse the token (without validating) to extract a value
var parsedToken = new JwtSecurityToken(securityToken);
var keyId = parsedToken.Claims.First(c => c.Type == "kid").Value;
// fetch JWKS and validate the token
var clientId = _configuration.GetSection("OidcProvider:ClientId").Get<string>();
var jwks = _httpClient.GetStringAsync($"url/to/jwks/{keyId}/{clientId}").Result;
// jwks == "{ keys: [..."
var signingKeys = new JsonWebKeySet(jwks).GetSigningKeys();
return _tokenHandler.ValidateToken(securityToken, new TokenValidationParameters
{
IssuerSigningKeys = signingKeys
}, out validatedToken);
}
public bool CanValidateToken { get; } = true;
public int MaximumTokenSizeInBytes { get; set; } = int.MaxValue;
}
它正在同步同步异步,这是不受欢迎的,但您可能想要缓存密钥以避免支付罚款。
我正在使用带有 Openid 的流程,我将我的用户重定向到另一个提供商,以处理登录问题,并且在这次登录之后,我在我的 /login/callback? 代码中收到了 code
=xxxx URL.
因此,JWT 是使用 code
生成的,但我无法验证它。我的 STS 中没有 well-known
端点,我需要以这种方式手动配置:
services.AddAuthorization(cfg =>
{
cfg.AddPolicy("MyPolicy", cfgPolicy =>
{
cfgPolicy.AddRequirements().RequireAuthenticatedUser();
cfgPolicy.AddAuthenticationSchemes(OpenIdConnectDefaults.AuthenticationScheme);
});
}).AddAuthentication(cfg =>
{
cfg.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(cfg =>
{
cfg.ClientId = authenticationConfig.ClientId;
cfg.ClientSecret = authenticationConfig.ClientSecret;
cfg.ResponseType = "code";
cfg.CallbackPath = "/login/callback";
cfg.Scope.Clear();
cfg.Scope.Add("openid");
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://myissuer"
};
cfg.Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://mysts/api/oauth/authorize",
TokenEndpoint = "https://mysts/api/oauth/token",
UserInfoEndpoint = "https://mysts/api/oauth/token_info"
};
});
一些要点:
- 我有一个令牌内省端点,用于验证我的令牌(token_info 端点)。
- 我没有 return public 键 (jwks) 的默认端点。我的端点始终是来自某些值的连接,类似 --> https://mysts/offline/jwks/{kid}/{clientid},所以这是动态的并且取决于令牌。
- 我没有众所周知的端点。
您无法验证它,因为 ASP.NET Core 默认会尝试验证 JWT 签名。但是,由于您自己设置了元数据,并且没有给出 JwksUri
,它无法获取 OIDC 提供商的 public 密钥来验证签名。
您可以指定JwksUri
,或关闭签名验证(不安全),或自行验证。如果您有权访问 public 密钥,请使用 options.SecurityTokenValidator
并实施 ISecurityTokenValidator
进行自定义验证。
这是一个动态获取 JWKS 并验证令牌的(未经测试的)实现:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddOpenIdConnect(options => {
options.SecurityTokenValidator = new MyJwtValidator(Configuration);
});
}
class MyJwtValidator: ISecurityTokenValidator
{
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient;
private readonly JwtSecurityTokenHandler _tokenHandler;
public MyJwtValidator(IConfiguration configuration)
{
_configuration = configuration;
_tokenHandler = new JwtSecurityTokenHandler();
_httpClient = new HttpClient
{
BaseAddress = new Uri(_configuration.GetSection("OidcProvider").Get<string>())
};
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(
string securityToken,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken
)
{
// parse the token (without validating) to extract a value
var parsedToken = new JwtSecurityToken(securityToken);
var keyId = parsedToken.Claims.First(c => c.Type == "kid").Value;
// fetch JWKS and validate the token
var clientId = _configuration.GetSection("OidcProvider:ClientId").Get<string>();
var jwks = _httpClient.GetStringAsync($"url/to/jwks/{keyId}/{clientId}").Result;
// jwks == "{ keys: [..."
var signingKeys = new JsonWebKeySet(jwks).GetSigningKeys();
return _tokenHandler.ValidateToken(securityToken, new TokenValidationParameters
{
IssuerSigningKeys = signingKeys
}, out validatedToken);
}
public bool CanValidateToken { get; } = true;
public int MaximumTokenSizeInBytes { get; set; } = int.MaxValue;
}
它正在同步同步异步,这是不受欢迎的,但您可能想要缓存密钥以避免支付罚款。