ASP.NET Core'da Jeton Bazlı Kimlik Doğrulama


161

ASP.NET Core uygulaması ile çalışıyorum. Belirteç Tabanlı Kimlik Doğrulama uygulamaya çalışıyorum ancak durumum için yeni Güvenlik Sisteminin nasıl kullanılacağını anlayamıyorum. Ben geçti örnekler ama bunlar ya çerez kimlik doğrulaması veya harici kimlik doğrulaması (GitHub Microsoft, Twitter) kullanırken, beni çok yardımcı olmadı.

Benim senaryom nedir: angularjs uygulaması /tokenurl geçen kullanıcı adı ve şifre talep etmelidir . WebApi access_token, aşağıdaki isteklerde angularjs uygulaması tarafından kullanılacak olan kullanıcıyı ve dönüşü yetkilendirmelidir .

ASP.NET - Token Tabanlı Kimlik Doğrulama'nın ASP.NET Web API 2, Owin ve Identity'yi kullanarak geçerli sürümünde tam olarak neye ihtiyacım olduğunu uygulamak hakkında harika bir makale buldum . Ama aynı şeyi ASP.NET Core'da nasıl yapacağım açık değil.

Benim sorum: ASP.NET Core WebApi uygulamasını belirteç tabanlı kimlik doğrulamasıyla çalışacak şekilde nasıl yapılandırabilirim?


Aynı sorunu yaşıyorum ve kendim yapmayı planlıyordum , FYI başka bir soru var stackoverflow.com/questions/29055477/… ama henüz bir cevap yok, ne olduğunu
görelim


Aynı sorunla da karşılaşıyorum ancak henüz bir çözüm bulamadım ... Jetonumu doğrulayan başka bir hizmet kullanarak özel bir kimlik doğrulaması yazmam gerekiyor.
Mayank Gupta

Yanıtlar:


137

Net Core 3.1 Güncellemesi:

David Fowler (ASP .NET Core ekibinin mimarı), JWT'yi gösteren basit bir uygulama da dahil olmak üzere inanılmaz derecede basit bir dizi görev uygulaması oluşturdu . Güncellemelerini ve basit stilini yakında bu yayına dahil edeceğim.

Net Core 2 için güncellendi:

Bu cevabın önceki versiyonlarında RSA kullanılmıştır; jeton üreten aynı kodun jetonları da doğrulaması gerçekten gerekli değildir. Ancak, sorumluluğu dağıtıyorsanız, muhtemelen bunu bir örneği kullanarak yapmak istersiniz Microsoft.IdentityModel.Tokens.RsaSecurityKey.

  1. Daha sonra kullanacağımız birkaç sabit oluşturun; yaptığım şey:

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
  2. Bunu Startup.cs'lerinize ekleyin ConfigureServices. Bu ayarlara erişmek için daha sonra bağımlılık enjeksiyonunu kullanacağız. Hata ayıklama ve üretim için farklı bir yapılandırmaya sahip olabileceğiniz authenticationConfigurationbir ConfigurationSectionveya Configurationnesne olduğunu varsayıyorum . Anahtarınızı güvenli bir şekilde sakladığınızdan emin olun! Herhangi bir dize olabilir.

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

    Diğer cevapların diğer ayarları değiştirdiğini gördüm ClockSkew; varsayılanlar, saatleri tam olarak senkronize olmayan dağıtılmış ortamlar için çalışacak şekilde ayarlanır. Değiştirmeniz gereken tek ayar bunlar.

  3. Kimlik Doğrulamasını ayarlayın. UserBilgilerinizi gerektiren herhangi bir ara yazılımdan önce bu satıra sahip olmalısınız app.UseMvc().

    app.UseAuthentication();

    Bunun jetonunuzun SignInManagerveya başka bir şeyle yayılmasına neden olmayacağını unutmayın . JWT'nizin çıkışını sağlamak için kendi mekanizmanızı sağlamalısınız - aşağıya bakın.

  4. Bir belirtmek isteyebilirsiniz AuthorizationPolicy. Bu, yalnızca Taşıyıcı belirteçlerinin kimlik doğrulaması olarak kullanılmasına izin veren denetleyicileri ve eylemleri belirtmenize olanak tanır [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
  5. İşte zor kısmı geliyor: jetonu inşa etmek.

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

    Ardından, jetonunuzu istediğiniz kontrol cihazınızda aşağıdakine benzer:

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

    Burada, zaten bir müdürünüz olduğunu varsayıyorum. Eğer Kimlik kullanıyorsanız, kullanabileceğiniz IUserClaimsPrincipalFactory<>kendi dönüşüm için Userbir içine ClaimsPrincipal.

  6. Test etmek için : Bir jeton alın, jwt.io'daki forma koyun . Yukarıda verdiğim talimatlar ayrıca imzayı doğrulamak için yapılandırmanızdaki sırrı kullanmanızı sağlar!

  7. Bunu, HTML sayfanızda .Net 4.5'te yalnızca taşıyıcı kimlik doğrulamasıyla birlikte kısmi görünümde oluşturuyorsanız, şimdi ViewComponentbunu yapmak için a'yı kullanabilirsiniz. Çoğunlukla yukarıdaki Denetleyici Eylem koduyla aynıdır.


1
IOptions<OAuthBearerAuthenticationOptions>Seçenekleri kullanmak için gerçekten enjekte etmeniz gerekir ; Seçenekler Modeli çerçevesi tarafından desteklenen adlandırılmış yapılandırma nedeniyle bir Options nesnesinin doğrudan kullanılması desteklenmez.
Matt DeKrey

2
Ne kullandığım için güncellendi, ancak şimdi cevap yeniden yazılmalı. Beni alay ettiğin için teşekkürler!
Matt DeKrey

5
# 5 o zamandan beri Microsoft.AspNet.Authentication.OAuthBearer - beta 5 - 6 ve muhtemelen daha önceki betalarda şu şekilde değiştirildi ancak bunları doğrulamadı. auth.AddPolicy ("Taşıyıcı", yeni AuthorizationPolicyBuilder () .AddAuthenticationSchemes (OAuthBearerAuthenticationDefaults.AuthenticationScheme) .RequireAuthenticatedUser (). Build ());
dynamiclynk

5
@MattDeKrey Bu yanıtı basit jeton tabanlı kimlik doğrulama örneği için başlangıç ​​noktası olarak kullandım ve beta 7'ye karşı çalışacak şekilde güncelledim - bkz. Github.com/mrsheepuk/ASPNETSelfCreatedTokenAuthExample - ayrıca bu yorumlardan birkaçını da içeriyor.
Mark Hughes

2
GitHub'daki şubelerde bulunan RC1 - Beta7 ve Beta8'in eski sürümleri için tekrar güncellendi .
Mark Hughes

83

Dan Çalışma Matt Dekrey muhteşem cevap , ben ASP.NET Çekirdek (1.0.1) karşı çalışan, belirteci tabanlı kimlik doğrulama bir dolu çalışma örneği oluşturduk. Kodun tamamını GitHub'daki ( 1.0.0-rc1 , beta8 , beta7 için alternatif dallar ) bu depoda bulabilirsiniz , ancak kısaca önemli adımlar şunlardır:

Uygulamanız için bir anahtar oluşturun

Örneğimde, uygulama her başlatıldığında rastgele bir anahtar oluşturuyorum, bir tane oluşturmanız ve bir yerde saklamanız ve uygulamanıza sağlamanız gerekir. Nasıl rastgele bir anahtar oluşturduğum ve bir .json dosyasından nasıl içe aktarabileceğiniz için bu dosyaya bakın . @Kspearrin tarafından yapılan yorumlarda da belirtildiği gibi, Veri Koruma API'sı anahtarları "doğru" yönetmek için ideal bir aday gibi görünüyor, ancak bu mümkün ise henüz çalışmadım. Çalışırsanız lütfen bir çekme isteği gönderin!

Startup.cs - ConfigureServices

Burada, belirteçlerimizin imzalanması için özel bir anahtar yüklememiz gerekir; bu anahtarlar, belirtildikçe belirteçleri doğrulamak için de kullanacağız. Anahtarı, keyaşağıdaki Yapılandırma yönteminde yeniden kullanacağımız sınıf düzeyinde bir değişkende saklıyoruz . TokenAuthOptions , anahtarlarımızı oluşturmak için TokenController'da ihtiyaç duyacağımız imza kimliğini, kitleyi ve yayıncıyı barındıran basit bir sınıftır.

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

Ayrıca [Authorize("Bearer")]korumak istediğimiz uç noktalar ve sınıflarda kullanmamıza izin vermek için bir yetkilendirme politikası oluşturduk .

Startup.cs - Yapılandır

Burada, JwtBearerAuthentication'ı yapılandırmamız gerekiyor:

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

Belirteç denetleyicisinde, Startup.cs dosyasına yüklenen anahtarı kullanarak imzalı anahtarlar oluşturmak için bir yönteminiz olması gerekir. Başlangıçta bir TokenAuthOptions örneği kaydettik, bu yüzden bunu TokenController için yapıcıya enjekte etmemiz gerekiyor:

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

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

Ardından, giriş bitiş noktası için işleyicinizde jetonu oluşturmanız gerekir, örneğimde bir kullanıcı adı ve şifre alıyorum ve bir if ifadesi kullanarak bunları onaylıyorum, ancak yapmanız gereken temel şey bir hak talebi oluşturmak veya yüklemek tabanlı kimlik ve bunun için jeton oluşturun:

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

Ve bu olmalı. [Authorize("Bearer")]Korumak istediğiniz herhangi bir yönteme veya sınıfa eklemeniz yeterlidir ve bir belirteç olmadan erişmeye çalışırsanız bir hata almalısınız. 500 hatası yerine 401 döndürmek istiyorsanız, burada örneğimde olduğu gibi özel bir özel durum işleyicisi kaydetmeniz gerekir .


1
Bu gerçekten mükemmel bir örnek ve @ MattDeKrey'in çalışmasını sağlamak için gereken tüm eksik parçaları dahil ettim, çok teşekkürler! Hala beta8 yerine beta7'yi hedefleyen herkesin github tarihinde
nickspoon

1
Sevindim @nickspoon yardımcı oldu - onunla herhangi bir sorun varsa, bana bildirin veya github bir çekme isteği pop ve ben güncelleme olacak!
Mark Hughes

2
Bunun için teşekkürler, ancak neden ASP.Net 4 Web API kutunun dışında çalıştı bir şey şimdi ASP.Net 5 oldukça yapılandırma biraz gerektiriyor anlamıyorum.
JMK

1
Sanırım ASP.NET 5 için gerçekten "sosyal kimlik doğrulaması" nı zorluyorlar, bu da sanırım bir anlam ifade ediyor, ancak uygun olmayan uygulamalar var, bu yüzden yönlerine katıldığımdan emin değilim @JMK
Mark Hughes

1
@YuriyP RC2 için bu cevabı güncellemem gerekiyor - Bunu RC2 için kullanan dahili uygulamamızı henüz güncellemedim, bu yüzden neyin dahil olduğundan emin değilim. Farkı
Mark Hughes

3

JWT Belirteçleri de dahil olmak üzere farklı kimlik doğrulama mekanizmalarının nasıl ele alınacağını gösteren OpenId bağlantı örneklerine göz atabilirsiniz:

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

Cordova Backend projesine bakarsanız, API yapılandırması aşağıdaki gibidir:

           // 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 ve bu projenin RessourceController içindeki mantık da;).

Alternatif olarak, jetonları doğrulamak için aşağıdaki kodu da kullanabilirsiniz (signalR ile çalışmasını sağlamak için bir snippet de vardır):

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

Jeton vermek için openId Connect sunucu paketlerini aşağıdaki gibi kullanabilirsiniz:

        // 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 ön uç çerçevesi ve ASP.NET çekirdeği kullanarak jeton tabanlı kimlik doğrulama uygulaması ile tek bir sayfa uygulaması gerçekleştirdik. Ayrıca bir sinyal R kalıcı bağlantısı vardır. Ancak herhangi bir DB uygulaması yapmadım. Kod burada görülebilir: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

Bu yardımcı olur umarım,

En iyi,

Alex


1

OpenIddict'a bir göz atın - ASP.NET 5'te JWT belirteçlerinin oluşturulmasını ve belirteçlerin yenilenmesini yapılandırmayı kolaylaştıran yeni bir projedir (yazma sırasında). Belirtilerin doğrulanması diğer yazılımlar tarafından gerçekleştirilir.

Kullanmak varsayarsak Identityile Entity Framework, son satır size eklemek istiyorum ne ConfigureServicesyöntemle:

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

İçinde Configure, OpenIddict'i JWT jetonlarını sunacak şekilde ayarladınız:

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

Ayrıca jetonların doğrulanmasını şu dillerde de yapılandırabilirsiniz 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 öğenizin OpenIddictContext öğesinden türetilmesi gibi bir veya iki küçük şey daha vardır.

Bu blog yazısında tam bir açıklama görebilirsiniz: http://capesean.co.za/blog/asp-net-5-jwt-tokens/

İşlevsel bir demo şu adreste bulunabilir: https://github.com/capesean/openiddict-test

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.