.net - jsonwebtoken - ¿Cómo configurar MIcrosoft JWT con clave simétrica?
jwt php (5)
Estoy intentando configurar mi aplicación ASP.NET para aceptar un token web JSON (JWT) que está firmado con una clave simétrica. El STS no es capaz de usar certificados para esto, por lo que estamos usando su soporte de clave simétrica.
Por mi parte, estoy usando la vista previa para desarrolladores JWT de Microsoft . Desafortunadamente, no he visto ningún ejemplo de cómo usar eso con una clave simétrica. Después de investigar con varias herramientas, encontré NamedKeyIssuerTokenResolver
y descubrí que puedo configurarlo para usar una clave simétrica. Por ejemplo:
<securityTokenHandlers>
<add type="Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler,Microsoft.IdentityModel.Tokens.JWT" />
<securityTokenHandlerConfiguration>
<certificateValidation certificateValidationMode="PeerTrust" />
<issuerTokenResolver
type="Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver,
Microsoft.IdentityModel.Tokens.JWT">
<securityKey
symmetricKey="+zqf97FD/xyzzyplugh42ploverFeeFieFoeFooxqjE="
name="https://localhost/TestRelyingParty" />
</issuerTokenResolver>
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
No estoy completamente seguro de qué debo usar para el name
allí. ¿Debería ser el público Uri, quizás el emisor Uri? En cualquier caso, sé que si no incluyo un name
, recibo una excepción cuando mi programa se inicia porque el elemento securityKey
requiere ese atributo.
En cualquier caso, esto todavía no resuelve el problema. Después de autenticarme contra el STS, obtengo la siguiente excepción:
[SecurityTokenValidationException: JWT10310: Unable to validate signature. validationParameters.SigningTokenResolver type: ''Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver'', was unable to resolve key to a token.
The SecurityKeyIdentifier is:
''SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 1,
Clause[0] = Microsoft.IdentityModel.Tokens.JWT.NamedKeyIdentifierClause
)
''. validationParameters.SigningToken was null.]
Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler.ValidateSignature(JWTSecurityToken jwt, TokenValidationParameters validationParameters) +2111
Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler.ValidateToken(JWTSecurityToken jwt, TokenValidationParameters validationParameters) +138
Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler.ValidateToken(SecurityToken token) +599
System.IdentityModel.Tokens.SecurityTokenHandlerCollection.ValidateToken(SecurityToken token) +135
System.IdentityModel.Services.TokenReceiver.AuthenticateToken(SecurityToken token, Boolean ensureBearerToken, String endpointUri) +117
System.IdentityModel.Services.WSFederationAuthenticationModule.SignInWithResponseMessage(HttpRequestBase request) +698
System.IdentityModel.Services.WSFederationAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs args) +123924
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +165
¿Me estoy perdiendo algún otro paso de configuración? ¿Estoy poniendo algo incorrecto en el atributo de name
? ¿O es este un error conocido en la vista previa del desarrollador JWT?
Actualización 2014/02/13:
Como @leastprivilege señala a continuación, esto es mucho más fácil con la versión RTM de JWT. Sugiero encarecidamente que ignore esto y siga el ejemplo que proporciona en http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/ .
Tenga en cuenta que la respuesta original a continuación fue para la versión Beta, Microsoft.IdentityModel.Tokens.JWT. La actualización a la versión de lanzamiento, System.IdentityModel.Tokens.Jwt, requería un poco más de trabajo. Vea abajo.
El problema principal es que el método JWTSecurityTokenHandler.ValidateToken(token)
no completa completamente los TokenValidationParameters
que pasa a JWTSecurityTokenHandler.ValidateToken(token, validationParameters)
. En particular, no llena el miembro SigningToken
o ValidIssuers
(o ValidIssuer
).
Curiosamente, la configuración que mostré en mi pregunta original en realidad está cargada por la resolución de tokens y está disponible en tiempo de ejecución, como puede ver en el código a continuación.
Sin embargo, no sé cómo especificar la cadena de emisor válida en el archivo de configuración. Sospecho firmemente que hay un lugar para poner esa información, pero aún no he descubierto a dónde pertenece.
La solución a mi problema es crear un controlador de token de seguridad personalizado que se derive de JWTSecurityTokenHandler
. La anulación de ValidateToken(token, validationParameters)
me da la oportunidad de establecer los parámetros que necesito y luego llamar al método ValidateToken
la clase base.
public class CustomJwtSecurityTokenHandler: JWTSecurityTokenHandler
{
// Override ValidateSignature so that it gets the SigningToken from the configuration if it doesn''t exist in
// the validationParameters object.
private const string KeyName = "https://localhost/TestRelyingParty";
private const string ValidIssuerString = "https://mySTSname/trust";
public override ClaimsPrincipal ValidateToken(JWTSecurityToken jwt, TokenValidationParameters validationParameters)
{
// set up valid issuers
if ((validationParameters.ValidIssuer == null) &&
(validationParameters.ValidIssuers == null || !validationParameters.ValidIssuers.Any()))
{
validationParameters.ValidIssuers = new List<string> {ValidIssuerString};
}
// and signing token.
if (validationParameters.SigningToken == null)
{
var resolver = (NamedKeyIssuerTokenResolver)this.Configuration.IssuerTokenResolver;
if (resolver.SecurityKeys != null)
{
List<SecurityKey> skeys;
if (resolver.SecurityKeys.TryGetValue(KeyName, out skeys))
{
var tok = new NamedKeySecurityToken(KeyName, skeys);
validationParameters.SigningToken = tok;
}
}
}
return base.ValidateToken(jwt, validationParameters);
}
}
En mi Web.config, solo tuve que cambiar el controlador de token de seguridad:
<securityTokenHandlers>
<!--<add type="Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler,Microsoft.IdentityModel.Tokens.JWT" />-->
<!-- replaces the default JWTSecurityTokenHandler -->
<add type="TestRelyingParty.CustomJwtSecurityTokenHandler,TestRelyingParty" />
Nada como pasar tres o cuatro días investigando un problema que se resuelve con un par de docenas de líneas de código. . .
Adición para nueva versión
En junio de 2013, Microsoft lanzó oficialmente su JWT. Cambiaron el espacio de nombres a System.IdentityModel.Tokens.Jwt. Después de actualizar a eso, la solución anterior dejó de funcionar. Para que funcione, tuve que agregar lo siguiente a mi CustomJwtSecurityTokenHandler
. Eso es además del código existente.
public override ClaimsPrincipal ValidateToken(JwtSecurityToken jwt)
{
var vparms = new TokenValidationParameters
{
AllowedAudiences = Configuration.AudienceRestriction.AllowedAudienceUris.Select(s => s.ToString())
};
return ValidateToken(jwt, vparms);
}
AFAIK, JWtSecurityTokenHandler aún no está listo para ser utilizado desde un archivo de configuración. El ejemplo dado por Vittorio Bertocci es también un "ejemplo de código". En eso, él llama explícitamente al ValidateToken sobrecargado con el parámetro tokenValidationParameters adicional que contiene todo lo necesario para hacer la validación (como la clave simétrica).
Desafortunadamente, esa sobrecarga no es llamada por la tubería normal de Wif (llama a ValidateToken con solo el token como parámetro) Resolví subclasificar el controlador de token jwtsecurity, anularé LoadCustomConfiguration para cargar manualmente el material necesario para crear un objeto toalemalidationParemeter (tuve para crear algunos objetos de configuración para esto). Luego hice una anulación de validateToken para llamar explícitamente a la sobrecarga con el parámetro adicional (que pude crear sobre la marcha con los parámetros que leí en la configuración). Todo muy engorroso de hacer, pero la única forma de aprovechar el poder de los parámetros de validación de token. (pero podría estar equivocado, por supuesto)
<issuerTokenResolver type="Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver, Microsoft.IdentityModel.Tokens.JWT">
<securityKey symmetricKey="01234567890123456789012345678901" name="MyIssuer"/>
</issuerTokenResolver>
<securityTokenHandlers>
Así es como funciona con la versión RTM del controlador JWT: http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/
Este es un ejemplo de uso de esta biblioteca con .Net 4.5 que emite y valida un JWT firmado con HMAC SHA256 basado en clave simétrica (todo en código y sin WIF):
string jwtIssuer = "MyIssuer";
string jwtAudience = "MyAudience";
// Generate symmetric key for HMAC-SHA256 signature
RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider();
byte[] keyForHmacSha256 = new byte[64];
cryptoProvider.GetNonZeroBytes(keyForHmacSha256);
///////////////////////////////////////////////////////////////////
// Create signing credentials for the signed JWT.
// This object is used to cryptographically sign the JWT by the issuer.
SigningCredentials sc = new SigningCredentials(
new InMemorySymmetricSecurityKey(keyForHmacSha256),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256");
///////////////////////////////////////////////////////////////////
// Create token validation parameters for the signed JWT
// This object will be used to verify the cryptographic signature of the received JWT
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
AllowedAudience = s_jwtAudience,
ValidIssuer = s_jwtIssuer,
ValidateExpiration = true,
ValidateNotBefore = true,
ValidateIssuer = true,
ValidateSignature = true,
SigningToken = new BinarySecretSecurityToken(keyForHmacSha256),
};
///////////////////////////////////////////////////////////////////
// Create JWT handler
// This object is used to write/sign/decode/validate JWTs
JWTSecurityTokenHandler jwtHandler = new JWTSecurityTokenHandler();
// Create a simple JWT claim set
IList<Claim> payloadClaims = new List<Claim>() { new Claim("clm1", "clm1 value"), };
// Create a JWT with signing credentials and lifetime of 12 hours
JWTSecurityToken jwt =
new JWTSecurityToken(jwtIssuer, jwtAudience, payloadClaims, sc, DateTime.UtcNow, DateTime.UtcNow.AddHours(12.0));
// Serialize the JWT
// This is how our JWT looks on the wire: <Base64UrlEncoded header>.<Base64UrlEncoded body>.<signature>
string jwtOnTheWire = jwtHandler.WriteToken(jwt);
// Validate the token signature (we provide the shared symmetric key in `validationParams`)
// This will throw if the signature does not validate
jwtHandler.ValidateToken(jwtOnTheWire, validationParams);
// Parse JWT from the Base64UrlEncoded wire form (<Base64UrlEncoded header>.<Base64UrlEncoded body>.<signature>)
JWTSecurityToken parsedJwt = jwtHandler.ReadToken(jwtOnTheWire) as JWTSecurityToken;
Jim
Gracias por probar la vista previa, lamento que haya tenido algunos problemas que no fueron obvios :-(.
El NamedKeyIssuerTokenResolver
nació de dos ideas:
- la necesidad de asociar una clave para verificar la firma que es un secreto compartido;
- Múltiples claves válidas podrían estar en uso al mismo tiempo.
Fue diseñado para funcionar con NamedKeySecurityToken
que tiene un nombre y varias claves. El NKITR
puede devolver un NKST
que simplifica la verificación de una firma cuando hay varias teclas en juego.
Uno de los objetivos de NKITR
era proporcionar una asignación entre la reclamación JWT iss
(en el encabezado) y una clave. Cuando es hora de verificar la firma, el JWTHandler
comprueba:
-
TokenValidationParamerter.SigningToken
, si lo encuentra,TokenValidationParamerter.SigningToken
; - Un
SecurityKeyIdentifier
obtenido deJWT.Header.SigningKeyIdentifier
(actualmente solo se admite x5t) se envía alINR
actual; - Se
NamedKeyIdentifierClause
unaNamedKeyIdentifierClause
desde elJwt.Issuer
y se envía alINR
actual.
Dado que un SecurityToken
puede contener varias claves, cada una en orden se usa para verificar la firma, las primeras paradas exitosas y el JWT.SigningToken
contendrá el SecurityToken
que validó la firma.
Jim y Willy,
Lo siento por la confusión con el método de sobrecarga ValidateToken(SecurityToken)
. Los parámetros se mueven de Configuration
a ValidationParameters
, pero no a las propiedades como ValidIssuer
que tienen un solo elemento, pero
IssuerNameRegistry -> VP.IssuerNameRegistry
IssuerTokenResolver -> VP.SigningTokenResolver
AllowedAudienceUris -> VP.AllowedAudiences
CertificateValidator -> VP.CertificateValidator
SaveBootStrapContext -> VP.SaveBootStrapContext
Brent