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

Posted

技术标签:

【中文标题】如何使用对称密钥配置 Microsoft JWT?【英文标题】:How to configure Microsoft JWT with symmetric key? 【发布时间】:2013-01-22 01:05:48 【问题描述】:

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

最后,我使用的是Microsoft's JWT Developer Preview。不幸的是,我还没有看到任何关于如何将其与对称密钥一起使用的示例。在使用各种工具进行了一些挖掘之后,我找到了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>

我不完全确定我应该为那里的name 使用什么。应该是受众Uri,还是发行者Uri?无论如何,我知道如果我不包含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

我是否缺少其他配置步骤?我在name 属性中放错东西了吗?或者这是 JWT 开发者预览版中的一个已知错误?

【问题讨论】:

【参考方案1】:

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) 方法没有完全填充它传递给JWTSecurityTokenHandler.ValidateToken(token, validationParameters)TokenValidationParameters。特别是,它不会填充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);
    

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

  <securityTokenHandlers>
    <!--<add type="Microsoft.IdentityModel.Tokens.JWT.JWTSecurityTokenHandler,Microsoft.IdentityModel.Tokens.JWT" />-->
    <!-- replaces the default JWTSecurityTokenHandler -->
    <add type="TestRelyingParty.CustomJwtSecurityTokenHandler,TestRelyingParty" />

没有什么比花三四天时间研究一个用几十行代码解决的问题更合适的了。 . .

新版本的补充

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);

【讨论】:

你刚刚把我从一个巨大的头痛中解救出来 - 已经为此奋斗了一天。如果可以的话,我会给你两次投票。 您好,动态添加 ValidAudience 很有趣,但它不适用于最后一个 Jwt。方法现在是:ReadOnlyCollection ValidateToken(SecurityToken token) @Jerome2606:如果您遇到问题,请发布一个新问题,也许有人会帮助您。自从我与 WIF 合作以来已经有好几年了,所以我并不了解最新的发展。【参考方案2】:

这是一个使用 .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;

【讨论】:

那么,如果我使用这个,我会在哪里做 app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions()...); ?还是我不需要这些? 这个解决方案效果很好,并且只需要适量的代码。我们中的许多人只需要验证生成的令牌,并且使用 JWT 的 NuGet 包和这个 sn-p 非常有效。验证令牌时只需根据自己的喜好自定义TokenValidationParameters(代码示例不是唯一的设置)。【参考方案3】:

吉姆,

感谢您试用预览版,很抱歉您遇到了一些不明显的问题 :-(。

NamedKeyIssuerTokenResolver 源于两个想法:

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

它旨在与具有名称和多个键的NamedKeySecurityToken 一起使用。 NKITR 可以返回一个 NKST,这可以在多个密钥在使用时简化签名检查。

NKITR 的一个目标是提供 JWT iss 声明(在标头中)和密钥之间的映射。当需要检查签名时,JWTHandler 会检查:

    TokenValidationParamerter.SigningToken,如果发现使用它; 从JWT.Header.SigningKeyIdentifier获取的SecurityKeyIdentifier(目前只支持x5t)发送到当前的INRNamedKeyIdentifierClauseJwt.Issuer 创建并发送到当前的 INR

由于SecurityToken 可以包含多个密钥,每个依次用于检查签名,第一个成功停止,JWT.SigningToken 将包含验证签名的SecurityToken

吉姆和威利,

对于与ValidateToken(SecurityToken) 重载方法的混淆,我们深表歉意。参数从Configuration移动到ValidationParameters,但不是像ValidIssuer这样有单个项目的属性,而是

IssuerNameRegistry -> VP.IssuerNameRegistry
IssuerTokenResolver -> VP.SigningTokenResolver
AllowedAudienceUris -> VP.AllowedAudiences
CertificateValidator -> VP.CertificateValidator
SaveBootStrapContext -> VP.SaveBootStrapContext

布伦特

【讨论】:

【参考方案4】:

AFAIK,JWtSecurityTokenHandler 尚未准备好从配置文件中使用。 Vittorio Bertocci 给出的示例也是一个“代码示例”。在那里,他使用附加的 tokenValidationParameters 参数显式调用重载的 ValidateToken,该参数包含进行验证所需的所有内容(如对称密钥)。 不幸的是,正常的 Wifi 管道不会调用该重载(它仅使用令牌作为参数调用 ValidateToken) 我决定继承 jwtsecurity 令牌处理程序,覆盖 LoadCustomConfiguration 以手动加载创建 tokenValidationParemeter 对象所需的东西(我必须为此创建一些配置对象)。然后我重写了 validateToken 以使用附加参数显式调用重载(我可以使用从配置中读取的参数即时创建)。所有这些都非常麻烦,但这是利用 tokenValidationparameters 功能的唯一方法。 (但我当然可能错了)

  <issuerTokenResolver type="Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver, Microsoft.IdentityModel.Tokens.JWT">
    <securityKey symmetricKey="01234567890123456789012345678901" name="MyIssuer"/>
  </issuerTokenResolver>
  <securityTokenHandlers>

【讨论】:

感谢您的描述。我希望避免子类化令牌处理程序。我要等到星期一才能看到这个。届时将回复。 非常有帮助。这让我走上了正确的轨道。有关完整的详细信息,请参阅我的答案。【参考方案5】:

以下是它与 JWT 处理程序的 RTM 版本的工作方式: http://leastprivilege.com/2013/07/16/identityserver-using-ws-federation-with-jwt-tokens-and-symmetric-signatures/

【讨论】:

看起来ValidatingIssuerNameRegistry 让事情变得更容易。我得试一试。谢谢。

以上是关于如何使用对称密钥配置 Microsoft JWT?的主要内容,如果未能解决你的问题,请参考以下文章

如何配置反应式资源服务器以使用具有对称密钥的 JWT?

JWT 密钥 - 非对称和对称

与对称密钥相比,用对称密钥签名JWT会更有用吗?

使用 .NET Core 中的公共安全密钥配置 JWT Bearer 令牌验证

带有护照 jwt 的非对称密钥。验证总是返回 Unauthorized

Microsoft 帐户 JWT 身份验证令牌如何签名?