如何使用对称密钥配置MIcrosoft JWT?

Jim*_*hel 17 .net wif jwt

我正在尝试将我的ASP.NET应用程序配置为接受使用对称密钥签名的JSON Web令牌(JWT).STS无法使用证书,所以我们使用它们的对称密钥支持.

在我的结尾,我正在使用微软的JWT开发者预览版.不幸的是,我没有看到任何关于如何使用对称密钥的示例.在使用各种工具进行一些挖掘后,我发现NamedKeyIssuerTokenResolver并发现我可以将其配置为使用对称密钥.例如:

<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>
Run Code Online (Sandbox Code Playgroud)

我不完全确定我应该在name那里使用什么.应该是观众Uri,也许是发行人Uri?无论如何,我知道如果我不包含a name,我的程序启动时会出现异常,因为该securityKey元素需要该属性.

无论如何,这仍然无法解决问题.在我对STS进行身份验证后,我得到以下异常:

[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
Run Code Online (Sandbox Code Playgroud)

我错过了其他一些配置步骤吗?我在name属性中添加了错误的东西吗?或者这是JWT开发者预览版中的已知错误?

Jim*_*hel 18

更新2014/02/13:

正如@leastprivilege在下面指出的那样,使用JWT的RTM版本可以轻松实现这一目标.我强烈建议您忽略这一点并使用他在http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/上提供的示例.

请注意,下面的原始答案是针对Beta版本的Microsoft.IdentityModel.Tokens.JWT.升级到发行版本System.IdentityModel.Tokens.Jwt,只需要更多的工作.见下文.

主要问题是该方法JWTSecurityTokenHandler.ValidateToken(token)没有完全填充TokenValidationParameters它传递给它JWTSecurityTokenHandler.ValidateToken(token, validationParameters).特别是,它不会填充SigningToken成员或ValidIssuers(或ValidIssuer).

有趣的是,我在原始问题中显示的配置实际上是由令牌解析器加载的,并且在运行时可用,如下面的代码所示.

但是,我不知道如何在配置文件中指定有效的颁发者字符串.我强烈怀疑有一个地方可以提供这些信息,但我还没弄清楚它属于哪里.

我的问题的解决方案是创建一个派生自定义安全令牌处理程序JWTSecurityTokenHandler.覆盖ValidateToken(token, validationParameters)使我有机会设置我需要的参数,然后调用基类的ValidateToken方法.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的Web.config中,我只需要更改安全性令牌处理程序:

  <securityTokenHandlers>
    <!--<add type="Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler,Microsoft.IdentityModel.Tokens.JWT" />-->
    <!-- replaces the default JWTSecurityTokenHandler -->
    <add type="TestRelyingParty.CustomJwtSecurityTokenHandler,TestRelyingParty" />
Run Code Online (Sandbox Code Playgroud)

没有什么比花费三到四天研究用几十行代码解决的问题了...

添加新版本

2013年6月,微软正式发布了他们的JWT.他们将名称空间更改为System.IdentityModel.Tokens.Jwt.升级到之后,上面的解决方案停止了工作.为了使它工作,我不得不将以下内容添加到我的CustomJwtSecurityTokenHandler.这是现有代码的补充.

public override ClaimsPrincipal ValidateToken(JwtSecurityToken jwt)
{
    var vparms = new TokenValidationParameters
        {
            AllowedAudiences = Configuration.AudienceRestriction.AllowedAudienceUris.Select(s => s.ToString())
        };
    return ValidateToken(jwt, vparms);
}
Run Code Online (Sandbox Code Playgroud)


小智 12

下面是该库与.Net 4.5的一个用法示例,它发布并验证使用基于对称密钥的HMAC SHA256签名的JWT(所有代码都没有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;
Run Code Online (Sandbox Code Playgroud)


Bre*_*ltz 5

吉姆

感谢您尝试预览,抱歉您有一些不明显的问题:-(.

NamedKeyIssuerTokenResolver从两个想法诞生了:

  1. 需要关联密钥以检查作为共享秘密的签名;
  2. 多个有效密钥可以同时使用.

它被设计为使用NamedKeySecurityToken具有名称和许多键的名称.该NKITR可以返回NKST当多个键在起作用从而简化了检查签名.

其中一个目标NKITR是在JWT iss声明(标题中)和密钥之间提供映射.在检查签名时,JWTHandler检查:

  1. TokenValidationParamerter.SigningToken,如果发现使用它;
  2. SecurityKeyIdentifierJWT.Header.SigningKeyIdentifier(当前仅支持x5t)获得的A 被发送到当前INR;
  3. A NamedKeyIdentifierClause是从中创建Jwt.Issuer并发送到当前的INR.

由于a SecurityToken可以包含多个密钥,每个密钥按顺序用于检查签名,首先成功停止,并且JWT.SigningToken将包含SecurityToken验证签名的密钥.

吉姆和威利,

很抱歉与ValidateToken(SecurityToken)重载方法混淆.参数从移动ConfigurationValidationParameters,但不喜欢的属性ValidIssuer具有单个项目,但

IssuerNameRegistry -> VP.IssuerNameRegistry
IssuerTokenResolver -> VP.SigningTokenResolver
AllowedAudienceUris -> VP.AllowedAudiences
CertificateValidator -> VP.CertificateValidator
SaveBootStrapContext -> VP.SaveBootStrapContext
Run Code Online (Sandbox Code Playgroud)

黑雁