Uwierzytelnianie oparte na tokenie w ASP.NET Rdzeń

Pracuję z ASP.NET Podstawowa aplikacja. Próbuję zaimplementować uwierzytelnianie oparte na tokenie, ale nie mogę dowiedzieć się, jak używać nowego systemu zabezpieczeń w moim przypadku. Przejrzałem przykłady , ale nie pomogły mi zbytnio, używają uwierzytelniania plików cookie lub uwierzytelniania zewnętrznego (GitHub, Microsoft, Twitter).

Jaki jest mój scenariusz: aplikacja angularjs powinna zażądać adresu URL przekazującego nazwę użytkownika i hasło. WebApi powinien autoryzować użytkownika i zwracać access_token, które będą używane przez aplikację angularjs w następujących żądaniach.

Znalazłem świetny artykuł o implementacji dokładnie tego, czego potrzebuję w obecnej wersji ASP.NET - uwierzytelnianie oparte na tokenie przy użyciu ASP.NET Web API 2, Owin i Identity . Ale nie jest dla mnie oczywiste, jak zrobić to samo w ASP.NET Rdzeń.

Moje pytanie brzmi: jak skonfigurować ASP.NET Podstawowa aplikacja WebApi do pracy z uwierzytelnianiem tokenowym?
Author: Cuong Le, 2015-03-14

4 answers

Aktualizacja dla. Net Core 2:

Poprzednie wersje tej odpowiedzi używały RSA; naprawdę nie jest to konieczne, jeśli ten sam kod, który generuje tokeny, również weryfikuje tokeny. Jeśli jednak rozpowszechniasz odpowiedzialność, prawdopodobnie nadal chcesz to zrobić za pomocą instancji Microsoft.IdentityModel.Tokens.RsaSecurityKey.

  1. Utwórz kilka stałych, których będziemy używać później; oto co zrobiłem:

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
    
  2. Dodaj to do swojego startupu.cs ' S ConfigureServices. Użyjemy zależności wstrzyknięcie później, aby uzyskać dostęp do tych ustawień. Zakładam, że twój authenticationConfiguration jest obiektem ConfigurationSection lub Configuration tak, że możesz mieć inny config do debugowania i produkcji. Upewnij się, że przechowujesz swój klucz bezpiecznie! Może to być dowolny ciąg.

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

    Widziałem inne odpowiedzi zmieniające inne ustawienia, takie jak ClockSkew; domyślne ustawienia są ustawione tak, że powinny działać w środowiskach rozproszonych, których zegary nie są dokładnie zsynchronizowane. Są to jedyne ustawienia, które musisz zmiana.

  3. Skonfiguruj uwierzytelnianie. Powinieneś mieć tę linię przed dowolnym oprogramowaniem pośredniczącym, które wymaga Twoich informacji User, takich jak app.UseMvc().

    app.UseAuthentication();
    

    Zauważ, że to nie spowoduje, że twój token zostanie wyemitowany z SignInManager lub czymkolwiek innym. Będziesz musiał podać swój własny mechanizm wyprowadzania JWT-patrz poniżej.

  4. Możesz podać AuthorizationPolicy. Pozwoli to określić kontrolery i akcje, które zezwalają tylko na tokeny okaziciela jako uwierzytelnianie za pomocą [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
    
  5. Nadchodzi trudna część: budowanie żetonu.

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

    Następnie, w kontrolerze, gdzie chcesz swój token, coś w stylu:

    [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);
    }
    
    Zakładam, że masz już dyrektora. Jeśli używasz tożsamości, możesz użyć IUserClaimsPrincipalFactory<> aby przekształcić swoje {[12] } w ClaimsPrincipal.
  6. Aby go przetestować : Pobierz token, umieść go w formularzu na jwt.io . The instrukcje, które podałem powyżej, pozwalają również użyć tajemnicy z twojego config, aby zweryfikować podpis!

  7. Jeśli renderowałeś to w częściowym widoku na stronie HTML w połączeniu z uwierzytelnianiem tylko na okaziciela w.Net 4.5, możesz teraz użyć ViewComponent, aby zrobić to samo. Jest w większości taki sam jak powyższy kod akcji kontrolera.

 107
Author: Matt DeKrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 21:38:45

Pracując z fantastyczną odpowiedzią Matta Dekreya , stworzyłem w pełni działający przykład uwierzytelniania tokenowego, działającego przeciwko ASP.NET Core (1.0.1). Pełny kod znajdziesz w repozytorium na GitHub (alternatywne gałęzie dla 1.0.0-rc1, beta8, beta7 ), ale w skrócie, ważne kroki to:

Wygeneruj klucz dla swojej aplikacji

W moim przykładzie generuję losowy klucz za każdym razem, gdy aplikacja się uruchamia, musisz go wygenerować, przechowywać gdzieś i dostarczyć do swojej aplikacji. zobacz ten plik, jak generuję losowy klucz i jak możesz go zaimportować z .plik json . Jak sugerowano w komentarzach @kspearrin, API Data Protection API wydaje się idealnym kandydatem do zarządzania kluczami "poprawnie", ale nie udało mi się jeszcze ustalić, czy jest to możliwe. Prześlij prośbę o wyciągnięcie, jeśli to rozwiążesz!

Startup.cs - ConfigureServices

Tutaj, musimy załadować klucz prywatny dla naszych tokenów do podpisania, który będziemy również używać do weryfikacji tokenów, jak są one prezentowane. Przechowujemy klucz w zmiennej na poziomie klasy key , którą użyjemy ponownie w poniższej metodzie Configure. TokenAuthOptions jest prostą klasą, która przechowuje tożsamość podpisu, odbiorców i emitenta, których będziemy potrzebować w Tokencontrolle do tworzenia naszych kluczy.

// 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());
});

Skonfigurowaliśmy również politykę autoryzacji, aby pozwól nam używać [Authorize("Bearer")] na punktach końcowych i klasach, które chcemy chronić.

Startup.cs-Configure

Tutaj musimy skonfigurować 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

W kontrolerze tokenu musisz mieć metodę generowania podpisanych kluczy za pomocą klucza, który został załadowany podczas uruchamiania.cs. Zarejestrowaliśmy instancję TokenAuthOptions w Startup, więc musimy wprowadzić ją do konstruktora dla TokenController:

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

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

Następnie będziesz musiał wygenerować token w programie obsługi punktu końcowego logowania, w moim przykładzie biorę nazwę użytkownika i hasło i sprawdzam je za pomocą instrukcji if, ale kluczową rzeczą, którą musisz zrobić, to utworzyć lub załadować tożsamość opartą na oświadczeniach i wygenerować token do tego:

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);
}
I to powinno być to. Po prostu dodaj [Authorize("Bearer")] do dowolnej metody lub klasy, którą chcesz chronić, a powinieneś uzyskać błąd, jeśli próbujesz uzyskać do niej dostęp bez obecności tokena. Jeśli chcesz zwrócić Błąd 401 zamiast 500, musisz zarejestrować niestandardową obsługę wyjątków , Jak mam w moim przykładzie tutaj.
 77
Author: Mark Hughes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:09

Możesz rzucić okiem na próbki OpenID connect, które ilustrują, jak radzić sobie z różnymi mechanizmami uwierzytelniania, w tym tokenami JWT:

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

Jeśli spojrzeć na projekt Backend Cordova, konfiguracja dla API jest tak:

           // 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"
            });
        });

Logika w /Providers / AuthorizationProvider.cs i RessourceController tego projektu są również warte obejrzenia ;).

Alternatywnie możesz również użyć następującego kodu do walidacji tokenów (istnieje również urywek, aby działał z 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);
                }
            };
        });

Do wystawienia tokena można użyć pakietów serwera openId Connect w następujący sposób:

        // 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: zaimplementowałem aplikację jednostronicową z implementacją uwierzytelniania opartego na tokenie przy użyciu frameworka Aurelia front end i ASP.NET rdzeń. Istnieje również sygnał r trwałe połączenie. Jednak nie zrobiłem żadnego DB wdrożenie. Kod można zobaczyć tutaj: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

Mam nadzieję, że to pomoże.]}

Najlepsze,

Alex

 3
Author: Darxtar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-20 08:35:21

Spójrz na OpenIddict - jest to nowy projekt (w momencie pisania), który ułatwia konfigurację tworzenia tokenów JWT i odświeżania tokenów w ASP.NET 5. Walidacja tokenów jest obsługiwana przez inne oprogramowanie.

Zakładając, że używasz Identity z Entity Framework, Ostatnia linia jest tym, co dodasz do swojej metody ConfigureServices:

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

W Configure, skonfigurowałeś OpenIddict do serwowania tokenów 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;
});

Konfigurujesz również walidację tokenów w 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/";
});

Tam są jedną lub dwiema innymi drobnymi rzeczami, takimi jak Twój DbContext musi pochodzić z OpenIddictContext.

Możesz zobaczyć pełne wyjaśnienie na tym blogu: http://capesean.co.za/blog/asp-net-5-jwt-tokens/

Funkcjonujące demo jest dostępne pod adresem: https://github.com/capesean/openiddict-test

 1
Author: Sean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-12 07:39:23