Я смотрю на исходный код класса JwtFormat, и мне интересно, почему он добавляет эмитента, который он восстанавливает из токена, в список ValidIssuers. Означает ли это, что он будет принимать всех эмитентов как действительных, если я не укажу ключ или не предоставлю обработчик IssueValidator для используемых TokenValidationParameters?
Кстати, я смотрю на этот класс, потому что изучаю проблему, касающуюся использования токенов JWT (azure ad v2.0) в приложении веб-API, которое, похоже, игнорирует свойство ValidIssuer:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {
AccessTokenFormat = new JwtFormat(
GetTokenValidationParameters(),
new OpenIdConnectCachingSecurityTokenProvider(authority)),
Provider = new OAuthBearerAuthenticationProvider {
OnValidateIdentity = ValidateIdentity
}
});
private TokenValidationParameters GetTokenValidationParameters() {
return new TokenValidationParameters {
ValidAudience = ConfigData.ClientId,
ValidIssuer = "nobody",
ValidIssuers = null,
IssuerValidator = ValidateIssuer
};
}
Я редактирую это, чтобы дать больше информации о том, что происходит.
Согласно исходному коду ValidateIssuer по умолчанию имеет значение true, поэтому нет необходимости устанавливать его снова. На всякий случай вот исходный код:
public TokenValidationParameters()
{
this.RequireExpirationTime = true;
this.RequireSignedTokens = true;
this.SaveSigninToken = false;
this.ValidateActor = false;
this.ValidateAudience = true;
this.ValidateIssuer = true;
this.ValidateIssuerSigningKey = false;
this.ValidateLifetime = true;
}
Я настраиваю IssuerValidator, потому что хочу убедиться, что, если установлен ValidIssuer, я хочу сравнить эмитента токена с этим значением (и не хочу проверять коллекцию ValidIssuers, когда проверка ValidIssuer терпит неудачу).
Если вам интересно, где заполняется ValidIssuers (и да, даже в моем примере он заполняется автоматически, хотя я установил для него explicityl значение null), это происходит в методе Unprotect JwtFormat:
public AuthenticationTicket Unprotect(string protectedText)
{
if (string.IsNullOrWhiteSpace(protectedText))
throw new ArgumentNullException(nameof (protectedText));
if (!(this.TokenHandler.ReadToken(protectedText) is JwtSecurityToken))
throw new ArgumentOutOfRangeException(nameof (protectedText), Microsoft.Owin.Security.Jwt.Properties.Resources.Exception_InvalidJwt);
TokenValidationParameters validationParameters = this._validationParameters;
if (this._issuerCredentialProviders != null)
{
validationParameters = validationParameters.Clone();
IEnumerable<string> second1 = this._issuerCredentialProviders.Select<IIssuerSecurityTokenProvider, string>((Func<IIssuerSecurityTokenProvider, string>) (provider => provider.Issuer));
validationParameters.ValidIssuers = validationParameters.ValidIssuers != null ? validationParameters.ValidIssuers.Concat<string>(second1) : second1;
IEnumerable<SecurityToken> second2 = this._issuerCredentialProviders.Select<IIssuerSecurityTokenProvider, IEnumerable<SecurityToken>>((Func<IIssuerSecurityTokenProvider, IEnumerable<SecurityToken>>) (provider => provider.SecurityTokens)).Aggregate<IEnumerable<SecurityToken>>((Func<IEnumerable<SecurityToken>, IEnumerable<SecurityToken>, IEnumerable<SecurityToken>>) ((left, right) => left.Concat<SecurityToken>(right)));
validationParameters.IssuerSigningTokens = validationParameters.IssuerSigningTokens != null ? validationParameters.IssuerSigningTokens.Concat<SecurityToken>(second2) : second2;
}
SecurityToken validatedToken;
ClaimsIdentity identity = (ClaimsIdentity) this.TokenHandler.ValidateToken(protectedText, validationParameters, out validatedToken).Identity;
AuthenticationProperties properties = new AuthenticationProperties();
if (this.UseTokenLifetime)
{
DateTime validFrom = validatedToken.ValidFrom;
if (validFrom != DateTime.MinValue)
properties.IssuedUtc = new DateTimeOffset?((DateTimeOffset) validFrom.ToUniversalTime());
DateTime validTo = validatedToken.ValidTo;
if (validTo != DateTime.MinValue)
properties.ExpiresUtc = new DateTimeOffset?((DateTimeOffset) validTo.ToUniversalTime());
properties.AllowRefresh = new bool?(false);
}
return new AuthenticationTicket(identity, properties);
}
Кстати, этот метод вызывает (косвенно) методом AuthenticateCoreAsync, когда ему нужно десериализовать токен:
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
string requestToken = (string) null;
string authorization = this.Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
requestToken = authorization.Substring("Bearer ".Length).Trim();
OAuthRequestTokenContext requestTokenContext = new OAuthRequestTokenContext(this.Context, requestToken);
await this.Options.Provider.RequestToken(requestTokenContext);
if (string.IsNullOrEmpty(requestTokenContext.Token))
return (AuthenticationTicket) null;
AuthenticationTokenReceiveContext tokenReceiveContext = new AuthenticationTokenReceiveContext(this.Context, this.Options.AccessTokenFormat, requestTokenContext.Token);
await this.Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
if (tokenReceiveContext.Ticket == null)
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
//remaining code removed
}
Поскольку я действительно не читал спецификации, мне было интересно, может ли кто-нибудь объяснить мне такое поведение (всегда добавлять эмитент токена в коллекцию ValidIssuers и проверять, находится ли эмитент токена в ValidIssuers - что всегда будет правдой!)
Окончательное редактирование
Ладно, плохо ... Не хватает кофе, я думаю ... На самом деле эмитент добавляется не из самого токена, а из IIssuerSecurityTokenProvider, который передается в ctor JwtFormat (получает его из конечной точки метаданных). ..
Извините ребята...
Спасибо.
Луис