问题
I'm trying to configure my ASP.NET app to accept a JSON Web Token (JWT) that is signed with a symmetric key. The STS isn't capable of using certificates for this, so we're using their symmetric key support.
On my end, I'm using Microsoft's JWT Developer Preview. Unfortunately, I've not seen any examples of how to use that with a symmetric key. After some digging around with various tools, I found the NamedKeyIssuerTokenResolver
and discovered that I can configure it to use a symmetric key. For example:
<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>
I'm not entirely sure what I'm supposed to use for the name
there. Should it be the audience Uri, perhaps the issuer Uri? In any case, I know that if I don't include a name
, I get an exception when my program starts because the securityKey
element requires that attribute.
Whatever the case, this still doesn't resolve the issue. After I authenticate against the STS, I get the following exception:
[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
Am I missing some other configuration step? Am I putting the wrong thing in the name
attribute? Or is this a known bug in the JWT Developer Preview?
回答1:
Update 2014/02/13:
As @leastprivilege points out below, this is a whole lot easier with the RTM version of the JWT. I strongly suggest that you ignore this and go with the example he provides at http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/.
Note that the original answer below was for the Beta version, Microsoft.IdentityModel.Tokens.JWT. Upgrading to the release version, System.IdentityModel.Tokens.Jwt, required just a little more work. See below.
The primary problem turns out to be that the method JWTSecurityTokenHandler.ValidateToken(token)
does not fully populate the TokenValidationParameters
that it passes to JWTSecurityTokenHandler.ValidateToken(token, validationParameters)
. In particular, it doesn't populate the SigningToken
member or the ValidIssuers
(or ValidIssuer
).
Interestingly, the configuration I showed in my original question actually is loaded by the token resolver, and is available at runtime, as you can see in the code below.
I don't know how to specify the valid issuer string in the configuration file, though. I strongly suspect that there's a place to put that info, but I haven't yet figured out where it belongs.
The solution to my problem is to create a custom security token handler that derives from JWTSecurityTokenHandler
. Overriding ValidateToken(token, validationParameters)
gives me the opportunity to set those parameters that I need, and then call the base class's ValidateToken
method.
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);
}
}
In my Web.config, I just had to change the security token handler:
<securityTokenHandlers>
<!--<add type="Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler,Microsoft.IdentityModel.Tokens.JWT" />-->
<!-- replaces the default JWTSecurityTokenHandler -->
<add type="TestRelyingParty.CustomJwtSecurityTokenHandler,TestRelyingParty" />
Nothing like spending three or four days researching a problem that is solved with a couple dozen lines of code . . .
Addition for new version
In June of 2013, Microsoft officially released their JWT. They changed the namespace to System.IdentityModel.Tokens.Jwt. After upgrading to that, the solution above stopped working. To get it working, I had to add the following to my CustomJwtSecurityTokenHandler
. That's in addition to the existing code.
public override ClaimsPrincipal ValidateToken(JwtSecurityToken jwt)
{
var vparms = new TokenValidationParameters
{
AllowedAudiences = Configuration.AudienceRestriction.AllowedAudienceUris.Select(s => s.ToString())
};
return ValidateToken(jwt, vparms);
}
回答2:
Here is a usage example of this library with .Net 4.5 that issues and validates a JWT signed with symmetric key based HMAC SHA256 (all in code and without 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;
回答3:
Jim,
Thanks for trying out the preview, sorry that you had some issues that weren't obvious :-(.
The NamedKeyIssuerTokenResolver
was born from two ideas:
- the need to associate a key for checking the signature that is a shared secret;
- multiple valid keys could be in use at the same time.
It was designed to work with the NamedKeySecurityToken
that has a name and a number of keys. The NKITR
can return a NKST
which simplifies checking a signature when multiple keys are in play.
One goal for the NKITR
was to provide a mapping between the JWT iss
claim (in the header) and a key. When it's time to check the signature, the JWTHandler
checks:
TokenValidationParamerter.SigningToken
, if found use it;- A
SecurityKeyIdentifier
obtained fromJWT.Header.SigningKeyIdentifier
(currently only x5t is supported) is sent to the currentINR
; - A
NamedKeyIdentifierClause
is created from theJwt.Issuer
and sent to the currentINR
.
Since a SecurityToken
can contain multiple keys, each one in order is used to check the signature, first success stops and the JWT.SigningToken
will contain the SecurityToken
that validated the signature.
Jim and Willy,
Sorry about the confusion with the ValidateToken(SecurityToken)
overload method. Parameters are moved from Configuration
to ValidationParameters
, but not the properties like ValidIssuer
that have a single item, but
IssuerNameRegistry -> VP.IssuerNameRegistry
IssuerTokenResolver -> VP.SigningTokenResolver
AllowedAudienceUris -> VP.AllowedAudiences
CertificateValidator -> VP.CertificateValidator
SaveBootStrapContext -> VP.SaveBootStrapContext
Brent
回答4:
AFAIK, the JWtSecurityTokenHandler is not yet ready to be used from a configuration file. The example given by Vittorio Bertocci is also a "code example". In that, he explicitly calls the overloaded ValidateToken with the additional tokenValidationParameters parameter that contains all stuff needed to do the validation (like the symmetric key).
Unfortunately, that overload is not called by the normal Wif pipeline (it calls the ValidateToken with just the token as a parameter)
I resolved to subclassing the jwtsecurity token handler, override LoadCustomConfiguration to manually load the stuff needed to create a tokenValidationParemeter object (I had to create some configuration objects for this). Then I did an override of validateToken to explictly call the overload with the additional parameter (which I could create on the fly with the parameters I read from the config). All very cumbersome to do but the only way to tap into the power of the tokenValidationparameters. (but I might be wrong of course)
<issuerTokenResolver type="Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver, Microsoft.IdentityModel.Tokens.JWT">
<securityKey symmetricKey="01234567890123456789012345678901" name="MyIssuer"/>
</issuerTokenResolver>
<securityTokenHandlers>
回答5:
Here is how it works with the RTM version of the JWT handler: http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/
来源:https://stackoverflow.com/questions/14735753/how-to-configure-microsoft-jwt-with-symmetric-key