Аутентификация на основе токенов в ASP.NET Керн

Я работаю с ASP.NET основное приложение. Я пытаюсь реализовать аутентификацию на основе токенов, но не могу понять, как использовать new Система Безопасности для моего случая. Я прошел через примеры но они мне не очень помогли, они используют либо аутентификацию cookie, либо внешнюю аутентификацию (GitHub, Microsoft, Twitter).

Каков мой сценарий: приложение angularjs должно запросить /token url-адрес, передающий имя пользователя и пароль. Веб-API должен авторизовать пользователя и вернуть access_token который будет использоваться приложением angularjs в следующих запросах.

Я нашел отличную статью о реализации именно того, что мне нужно в текущей версии ASP.NET -аутентификация на основе токенов с использованием ASP.NET Web API 2, Owin и Identity. Но для меня не очевидно, как сделать то же самое в ASP.NET ядро.

мой вопрос: Как настроить ASP.NET основное приложение WebApi для работы с аутентификацией на основе токенов?

4 ответов


обновлено для .Net Core 2:

предыдущие версии этого ответа использовали RSA; это действительно не обязательно, если ваш же код, который генерирует токены, также проверяет токены. Однако, если вы распределяете ответственность, вы, вероятно, все еще хотите сделать это, используя экземпляр Microsoft.IdentityModel.Tokens.RsaSecurityKey.

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

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
    
  2. добавьте это к вашему Запуск.в CS ConfigureServices. Мы будем использовать инъекцию зависимостей позже для доступа к этим настройкам. Я предполагаю, что ваш authenticationConfiguration это ConfigurationSection или Configuration объект такой, что вы можете иметь другую конфигурацию для отладки и производства. Убедитесь, что ваш ключ надежно хранится! Это может быть любая строка.

    var keySecret = authenticationConfiguration["JwtSigningKey"];
    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret));
    
    services.AddTransient(_ => new JwtSignInHandler(symmetricKey));
    
    services.AddAuthentication(options =>
    {
        // This causes the default authentication scheme to be JWT.
        // Without this, the Authorization header is not checked and
        // you'll get no results. However, this also means that if
        // you're already using cookies in your app, they won't be 
        // checked by default.
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters.ValidateIssuerSigningKey = true;
            options.TokenValidationParameters.IssuerSigningKey = symmetricKey;
            options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
            options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
        });
    

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

  3. настройка аутентификации. Вы должны иметь эту строку перед любым промежуточным программным обеспечением, которое требует вашего User информация, например:app.UseMvc().

    app.UseAuthentication();
    

    обратите внимание, что это не приведет к тому, что ваш токен будет излучаться с SignInManager или что-нибудь еще. Вам нужно будет предоставить свой собственный механизм для вывода вашего JWT-см. ниже.

  4. вы можете указать AuthorizationPolicy. Это позволит вам чтобы указать контроллеры и действия, которые разрешают только токены предъявителя в качестве аутентификации с помощью [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
    
  5. вот сложная часть: создание токена.

    class JwtSignInHandler
    {
        public const string TokenAudience = "Myself";
        public const string TokenIssuer = "MyProject";
        private readonly SymmetricSecurityKey key;
    
        public JwtSignInHandler(SymmetricSecurityKey symmetricKey)
        {
            this.key = symmetricKey;
        }
    
        public string BuildJwt(ClaimsPrincipal principal)
        {
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(
                issuer: TokenIssuer,
                audience: TokenAudience,
                claims: principal.Claims,
                expires: DateTime.Now.AddMinutes(20),
                signingCredentials: creds
            );
    
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
    

    затем, в вашем контроллере, где вы хотите свой токен, что-то вроде следующего:

    [HttpPost]
    public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory)
    {
        var principal = new System.Security.Claims.ClaimsPrincipal(new[]
        {
            new System.Security.Claims.ClaimsIdentity(new[]
            {
                new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User")
            })
        });
        return tokenFactory.BuildJwt(principal);
    }
    

работает с Мэтт Dekrey это, Я создал полностью рабочий пример аутентификации на основе токенов, работающий против ASP.NET Core (1.0.1). Вы можете найти полный код в этом репозитории на GitHub (альтернативные ветки для 1.0.0-rc1 и, beta8, beta7), но вкратце, важными шагами являются:

создать ключ для вашего приложения

в моем примере, я генерируйте случайный ключ каждый раз, когда приложение запускается, вам нужно сгенерировать его и сохранить его где-нибудь и предоставить его приложению. см. этот файл для того, как я генерирую случайный ключ и как вы можете импортировать его из a .файл json. Как предложено в комментариях @kspearrin,API защиты данных кажется идеальным кандидатом для управления ключами "правильно", но я еще не разработал, возможно ли это. Пожалуйста, отправьте запрос на вытягивание, если вы его разработаете!

Автозагрузка.cs-ConfigureServices

здесь нам нужно загрузить закрытый ключ для наших токенов, которые будут подписаны, который мы также будем использовать для проверки токенов по мере их представления. Мы храним ключ в переменной уровня класса key который мы будем повторно использовать в методе Configure ниже. TokenAuthOptions это простой класс, который содержит идентификатор подписи, аудиторию и эмитента, который нам понадобится в TokenController для создания нашего ключи.

// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();

// Create the key, and a set of token options to record signing credentials 
// using that key, along with the other parameters we will need in the 
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
    Audience = TokenAudience,
    Issuer = TokenIssuer,
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};

// Save the token options into an instance so they're accessible to the 
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);

// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
        .RequireAuthenticatedUser().Build());
});

мы также создать политику авторизации, чтобы позволить нам использовать [Authorize("Bearer")] на конечных точках и классах, которые мы хотим защитить.

Автозагрузка.cs-Configure

здесь нам нужно настроить JwtBearerAuthentication:

app.UseJwtBearerAuthentication(new JwtBearerOptions {
    TokenValidationParameters = new TokenValidationParameters {
        IssuerSigningKey = key,
        ValidAudience = tokenOptions.Audience,
        ValidIssuer = tokenOptions.Issuer,

        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,

        // This defines the maximum allowable clock skew - i.e.
        // provides a tolerance on the token expiry time 
        // when validating the lifetime. As we're creating the tokens 
        // locally and validating them on the same machines which 
        // should have synchronised time, this can be set to zero. 
        // Where external tokens are used, some leeway here could be 
        // useful.
        ClockSkew = TimeSpan.FromMinutes(0)
    }
});

TokenController

в контроллере токенов вам нужно иметь метод для создания подписанных Ключей с использованием ключа, загруженного при запуске.цезий. Мы зарегистрировал экземпляр TokenAuthOptions при запуске, поэтому нам нужно ввести его в конструктор для TokenController:

[Route("api/[controller]")]
public class TokenController : Controller
{
    private readonly TokenAuthOptions tokenOptions;

    public TokenController(TokenAuthOptions tokenOptions)
    {
        this.tokenOptions = tokenOptions;
    }
...

тогда вам нужно будет сгенерировать токен в вашем обработчике для конечной точки входа в систему, в моем примере я беру имя пользователя и пароль и проверяю их с помощью оператора if, но главное, что вам нужно сделать, это создать или загрузить удостоверение на основе утверждений и сгенерировать токен для этого:

public class AuthRequest
{
    public string username { get; set; }
    public string password { get; set; }
}

/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
    // Obviously, at this point you need to validate the username and password against whatever system you wish.
    if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
    {
        DateTime? expires = DateTime.UtcNow.AddMinutes(2);
        var token = GetToken(req.username, expires);
        return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
    }
    return new { authenticated = false };
}

private string GetToken(string user, DateTime? expires)
{
    var handler = new JwtSecurityTokenHandler();

    // Here, you should create or look up an identity for the user which is being authenticated.
    // For now, just creating a simple generic identity.
    ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });

    var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
        Issuer = tokenOptions.Issuer,
        Audience = tokenOptions.Audience,
        SigningCredentials = tokenOptions.SigningCredentials,
        Subject = identity,
        Expires = expires
    });
    return handler.WriteToken(securityToken);
}

и это должно быть так. Просто добавьте [Authorize("Bearer")] для любой метод или класс, который вы хотите защитить, и вы должны получить ошибку, если вы попытаетесь получить к нему доступ без токена. Если вы хотите вернуть 401 вместо 500 ошибок, вам нужно зарегистрировать пользовательский обработчик исключений а у меня в моем примере здесь.


вы можете взглянуть на образцы OpenId connect, которые иллюстрируют, как обращаться с различными механизмами аутентификации, включая токены JWT:

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples

Если вы посмотрите на проект бэкэнда Cordova, конфигурация для API выглядит так:

           // Create a new branch where the registered middleware will be executed only for non API calls.
        app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch => {
            // Insert a new cookies middleware in the pipeline to store
            // the user identity returned by the external identity provider.
            branch.UseCookieAuthentication(new CookieAuthenticationOptions {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                AuthenticationScheme = "ServerCookie",
                CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie",
                ExpireTimeSpan = TimeSpan.FromMinutes(5),
                LoginPath = new PathString("/signin"),
                LogoutPath = new PathString("/signout")
            });

            branch.UseGoogleAuthentication(new GoogleOptions {
                ClientId = "560027070069-37ldt4kfuohhu3m495hk2j4pjp92d382.apps.googleusercontent.com",
                ClientSecret = "n2Q-GEw9RQjzcRbU3qhfTj8f"
            });

            branch.UseTwitterAuthentication(new TwitterOptions {
                ConsumerKey = "6XaCTaLbMqfj6ww3zvZ5g",
                ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI"
            });
        });

логика в/Providers / AuthorizationProvider.cs и RessourceController этого проекта также стоят посмотрю ;).

В качестве альтернативы вы также можете использовать следующий код для проверки токенов (есть также фрагмент, чтобы заставить его работать с signalR):

        // Add a new middleware validating access tokens.
        app.UseOAuthValidation(options =>
        {
            // Automatic authentication must be enabled
            // for SignalR to receive the access token.
            options.AutomaticAuthenticate = true;

            options.Events = new OAuthValidationEvents
            {
                // Note: for SignalR connections, the default Authorization header does not work,
                // because the WebSockets JS API doesn't allow setting custom parameters.
                // To work around this limitation, the access token is retrieved from the query string.
                OnRetrieveToken = context =>
                {
                    // Note: when the token is missing from the query string,
                    // context.Token is null and the JWT bearer middleware will
                    // automatically try to retrieve it from the Authorization header.
                    context.Token = context.Request.Query["access_token"];

                    return Task.FromResult(0);
                }
            };
        });

для выдачи токена вы можете использовать пакеты openId Connect server следующим образом:

        // Add a new middleware issuing access tokens.
        app.UseOpenIdConnectServer(options =>
        {
            options.Provider = new AuthenticationProvider();
            // Enable the authorization, logout, token and userinfo endpoints.
            //options.AuthorizationEndpointPath = "/connect/authorize";
            //options.LogoutEndpointPath = "/connect/logout";
            options.TokenEndpointPath = "/connect/token";
            //options.UserinfoEndpointPath = "/connect/userinfo";

            // Note: if you don't explicitly register a signing key, one is automatically generated and
            // persisted on the disk. If the key cannot be persisted, an exception is thrown.
            // 
            // On production, using a X.509 certificate stored in the machine store is recommended.
            // You can generate a self-signed certificate using Pluralsight's self-cert utility:
            // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip
            // 
            // options.SigningCredentials.AddCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75");
            // 
            // Alternatively, you can also store the certificate as an embedded .pfx resource
            // directly in this assembly or in a file published alongside this project:
            // 
            // options.SigningCredentials.AddCertificate(
            //     assembly: typeof(Startup).GetTypeInfo().Assembly,
            //     resource: "Nancy.Server.Certificate.pfx",
            //     password: "Owin.Security.OpenIdConnect.Server");

            // Note: see AuthorizationController.cs for more
            // information concerning ApplicationCanDisplayErrors.
            options.ApplicationCanDisplayErrors = true // in dev only ...;
            options.AllowInsecureHttp = true // in dev only...;
        });

EDIT: я реализовал одностраничное приложение с реализацией аутентификации на основе токенов с использованием платформы Aurelia front end и ASP.NET ядро. Существует также сигнал R постоянное соединение. Однако я не сделал никакой реализации DB. Код можно посмотреть здесь: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

надеюсь, это поможет,

лучший

Алекс


посмотрите на OpenIddict-это новый проект (на момент написания), который позволяет легко настроить создание токенов JWT и обновить токены в ASP.NET 5. Проверка токенов осуществляется другим программным обеспечением.

если вы используете Identity С Entity Framework последняя строка-это то, что вы хотели добавить в свой ConfigureServices способ:

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddOpenIddictCore<Application>(config => config.UseEntityFramework());

на Configure, вы настроили OpenIddict для обслуживания токенов JWT:

app.UseOpenIddictCore(builder =>
{
    // tell openiddict you're wanting to use jwt tokens
    builder.Options.UseJwtTokens();
    // NOTE: for dev consumption only! for live, this is not encouraged!
    builder.Options.AllowInsecureHttp = true;
    builder.Options.ApplicationCanDisplayErrors = true;
});

вы также настраиваете проверку токены в Configure:

// use jwt bearer authentication
app.UseJwtBearerAuthentication(options =>
{
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.RequireHttpsMetadata = false;
    options.Audience = "http://localhost:58292/";
    options.Authority = "http://localhost:58292/";
});

есть одна или две другие второстепенные вещи, такие как ваш DbContext должен быть производным от OpenIddictContext.

вы можете увидеть полное объяснение длины на этом блоге:http://capesean.co.za/blog/asp-net-5-jwt-tokens/

демо-версия доступна по адресу:https://github.com/capesean/openiddict-test