net jsonwebtoken example asp .net wif jwt

.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>



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:

  1. la necesidad de asociar una clave para verificar la firma que es un secreto compartido;
  2. 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:

  1. TokenValidationParamerter.SigningToken , si lo encuentra, TokenValidationParamerter.SigningToken ;
  2. Un SecurityKeyIdentifier obtenido de JWT.Header.SigningKeyIdentifier (actualmente solo se admite x5t) se envía al INR actual;
  3. Se NamedKeyIdentifierClause una NamedKeyIdentifierClause desde el Jwt.Issuer y se envía al INR 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