Как настроить MIcrosoft JWT с симметричным ключом?

17

Я пытаюсь настроить приложение ASP.NET, чтобы принять JNON Web Token (JWT), подписанный симметричным ключом. STS не может использовать сертификаты для этого, поэтому мы используем поддержку симметричного ключа.

В конце концов, я использую Предварительный просмотр Microsoft 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>

Я не совсем уверен, что я должен использовать для name . Должна ли быть аудитория Ури, возможно, эмитентом Ури? В любом случае, я знаю, что если я не включаю 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 Developer Preview?

    
задан Jim Mischel 06.02.2013 в 19:00
источник

5 ответов

18

Обновление 2014/02/13:

Как видно из нижеприведенного определения @leastprivilege, это намного проще с RTM-версией JWT. Я настоятельно рекомендую вам проигнорировать это и перейти к примеру, который он предоставляет в Ссылка .

Обратите внимание, что исходный ответ был приведен для бета-версии 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);
    }
}

В моем Web.config мне просто пришлось изменить обработчик маркера безопасности:

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

Ничего, как потратить три или четыре дня на исследование проблемы, которая решена с помощью пары дюжины строк кода. , .

Дополнение для новой версии

В июне 2013 года Microsoft официально выпустила свой 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);
}
    
ответ дан Jim Mischel 12.02.2013 в 18:08
  • Большое спасибо за фрагмент кода, именно то, что мне нужно –  emp 17.02.2013 в 13:46
  • Ты только спас меня от огромной головной боли - сражался с этим уже целый день. Если бы я мог, я бы вас дважды повысил. –  E.Z. Hart 30.08.2013 в 04:02
  • Здравствуйте, интересно добавить ValidAudience на лету, но он не работает с последним Jwt. Теперь метод: ReadOnlyCollection <ClaimsIdentity> ValidateToken (маркер SecurityToken) –  Jerome2606 19.04.2016 в 12:38
  • @ Jerome2606: Если у вас возникли проблемы, отправьте новый вопрос, и, возможно, кто-то поможет вам. Прошло несколько лет с тех пор, как я работал с WIF, поэтому я не нахожусь на вершине последних разработок. –  Jim Mischel 19.04.2016 в 14:26
12

Вот пример использования этой библиотеки с .Net 4.5, который выдает и проверяет JWT, подписанный с использованием HMAC SHA256 с симметричным ключом (все в коде и без 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;
    
ответ дан Kastorskij 12.02.2013 в 15:30
  • Итак, если я использую это, где я буду делать app.UseJwtBearerAuthentication (новый JwtBearerAuthenticationOptions () {...}); ? Или мне это не нужно? –  Narayana 31.03.2014 в 13:50
  • Это решение работает очень хорошо и является правильным количеством необходимого кода. Многим из нас просто нужно проверять сгенерированный токен, и использование пакета NuGet для JWT вместе с этим фрагментом работает фантастически. Просто настройте параметры TokenValidationParameters по вашему вкусу при проверке токена (пример кода - не единственная настройка). –  atconway 01.09.2015 в 00:01
5

Джим

Спасибо, что попробовали предварительный просмотр, извините, что у вас были некоторые проблемы, которые не были очевидны: - (.

NamedKeyIssuerTokenResolver родилось из двух идей:

  1. необходимо связать ключ для проверки подписи, являющейся общим секретом;
  2. несколько действительных ключей могут использоваться одновременно.

Он был разработан для работы с NamedKeySecurityToken , у которого есть имя и несколько ключей. NKITR может вернуть NKST , что упрощает проверку подписи при игре нескольких ключей.

Одна из целей для NKITR заключалась в предоставлении сопоставления между заявкой JWT iss (в заголовке) и ключом. Когда пришло время проверить подпись, JWTHandler проверяет:

  1. TokenValidationParamerter.SigningToken , если его использовать,
  2. A SecurityKeyIdentifier , полученное из JWT.Header.SigningKeyIdentifier (поддерживается только x5t), отправляется текущему INR ;
  3. A NamedKeyIdentifierClause создается из Jwt.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

Брент

    
ответ дан Brent Schmaltz 08.03.2013 в 08:51
4

AFAIK, JWtSecurityTokenHandler еще не готов к использованию из файла конфигурации. Пример, приведенный Витторио Берточчи, также является «примером кода». В этом случае он явно вызывает перегруженный ValidateToken с дополнительным параметром tokenValidationParameters, который содержит все необходимое для проверки (например, симметричный ключ).
К сожалению, эта перегрузка не вызывается обычным конвейером Wif (он вызывает ValidateToken с помощью только токена в качестве параметра) Я решил подклассифицировать обработчик маркера jwtsecurity, переопределить LoadCustomConfiguration, чтобы вручную загрузить материал, необходимый для создания объекта tokenValidationParemeter (мне пришлось создать для него некоторые объекты конфигурации). Затем я сделал переопределение validateToken, чтобы явно вызвать перегрузку с дополнительным параметром (который я мог бы создать «на лету» с параметрами, которые я читал из конфигурации). Все очень громоздко, но это единственный способ задействовать силу параметров tokenValidation. (но я, возможно, ошибаюсь, конечно)

  <issuerTokenResolver type="Microsoft.IdentityModel.Tokens.JWT.NamedKeyIssuerTokenResolver, Microsoft.IdentityModel.Tokens.JWT">
    <securityKey symmetricKey="01234567890123456789012345678901" name="MyIssuer"/>
  </issuerTokenResolver>
  <securityTokenHandlers>
    
ответ дан Willy Van den Driessche 09.02.2013 в 15:51
  • Спасибо за это описание. Я надеялся избежать подклассификации обработчика токенов. Будет понедельник, прежде чем я смогу посмотреть на это. Тогда ответит. –  Jim Mischel 09.02.2013 в 20:47
  • Очень полезно. Это поставило меня на правильный путь. См. Мой ответ для получения полной информации. –  Jim Mischel 12.02.2013 в 18:09
3

Вот как это работает с RTM-версией обработчика JWT: Ссылка

    
ответ дан leastprivilege 16.07.2013 в 09:34
  • Похоже, что ValidatingIssuerNameRegistry делает вещи намного проще. Я должен буду попробовать. Благодарю. –  Jim Mischel 16.07.2013 в 15:09