2017-08-16 47 views
10

Wydaje mi się, że miałem prosty cel w głowie, kiedy wyruszyłem dzień temu, aby zaimplementować niezależną witrynę internetową z autorskim interfejsem .NET core 2.0, ale jeszcze nie mam wszystko zdalnie działa. Oto lista tego, co próbuję zrobić:Identyfikator właściciela ASP.NET Core 2.0 bez tożsamości

  • wdrożyć okaziciela żeton chroniony WebAPI
  • Emisji tokeny & żetony odświeżania z punktem końcowym w tym samym projekcie
  • użyj przycisków [Autoryzacja] przypisują kontrolować Dostęp do powierzchni api
  • Nie używać ASP.Net tożsamości (mam dużo lżejszy użytkownika waga/członkostwa reqs)

jestem całkowicie w porządku z budynku tożsamości/roszczeń/zamawiającego w loginu i dodając, że aby zażądać kontekstu, ale nie widziałem ani jednego przykładu na wydawanie i używanie auth/odświeżania tokenów w webapi Core 2.0 bez Tożsamości. Widziałem przykład 1.x MSDN plików cookie bez Tożsamości, ale nie zrozumiałem wystarczająco daleko, aby spełnić powyższe wymagania.

Czuję, że może to być typowy scenariusz i nie powinno to być takie trudne (może nie jest, może po prostu brak dokumentacji/przykładów?). O ile wiem, IdentityServer4 nie jest zgodny z Core 2.0 Auth, opendiddict wydaje się wymagać Tożsamości. Nie chcę również hostować tokena w oddzielnym procesie, ale w tej samej instancji webapi.

Czy ktoś może wskazać mi konkretny przykład lub przynajmniej podać wskazówki dotyczące najlepszych kroków/opcji?

+0

Chciałbym zobaczyć próbkę tego również. –

+0

Tożsamość jest oddzielona od mechanizmu JWT. Przeczytaj [to] (https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x) i [to] (https://pioneercode.com/post/ uwierzytelnianie-w-asp-dot-net-core-api-part-3-json-web-token). Pozdrowienia. –

Odpowiedz

9

Dokonano edycji, aby była zgodna z programem ASP.NET Core 2.0.


Po pierwsze, niektóre pakiety Nuget:

  • Microsoft.AspNetCore.Authentication.JwtBearer
  • Microsoft.AspNetCore.Identity
  • System.IdentityModel.Tokens.Jwt
  • System.Security .Cryptography.Csp

Th en niektóre podstawowe obiekty do przesyłania danych.

// Presumably you will have an equivalent user account class with a user name. 
public class User 
{ 
    public string UserName { get; set; } 
} 

public class JsonWebToken 
{ 
    public string access_token { get; set; } 

    public string token_type { get; set; } = "bearer"; 

    public int expires_in { get; set; } 

    public string refresh_token { get; set; } 
} 

dojazd do poprawnego działania, trzeba mieć login/tokena metodę internetową, aby rzeczywiście wysłać token autoryzacji dla użytkownika.

[Route("api/token")] 
public class TokenController : Controller 
{ 
    private ITokenProvider _tokenProvider; 

    public TokenController(ITokenProvider tokenProvider) // We'll create this later, don't worry. 
    { 
     _tokenProvider = tokenProvider; 
    } 

    public JsonWebToken Get([FromQuery] string grant_type, [FromQuery] string username, [FromQuery] string password, [FromQuery] string refresh_token) 
    { 
     // Authenticate depending on the grant type. 
     User user = grant_type == "refresh_token" ? GetUserByToken(refresh_token) : GetUserByCredentials(username, password); 

     if (user == null) 
      throw new UnauthorizedAccessException("No!"); 

     int ageInMinutes = 20; // However long you want... 

     DateTime expiry = DateTime.UtcNow.AddMinutes(ageInMinutes); 

     var token = new JsonWebToken { 
      access_token = _tokenProvider.CreateToken(user, expiry), 
      expires_in = ageInMinutes * 60 
     }; 

     if (grant_type != "refresh_token") 
      token.refresh_token = GenerateRefreshToken(user); 

     return token; 
    } 

    private User GetUserByToken(string refreshToken) 
    { 
     // TODO: Check token against your database. 
     if (refreshToken == "test") 
      return new User { UserName = "test" }; 

     return null; 
    } 

    private User GetUserByCredentials(string username, string password) 
    { 
     // TODO: Check username/password against your database. 
     if (username == password) 
      return new User { UserName = username }; 

     return null; 
    } 

    private string GenerateRefreshToken(User user) 
    { 
     // TODO: Create and persist a refresh token. 
     return "test"; 
    } 
} 

Zapewne zauważyłeś, że tworzenie tokena to wciąż "magia" przekazywana przez jakiegoś wyimaginowanego ITokenProvider. Zdefiniuj interfejs dostawcy tokenu.

public interface ITokenProvider 
{ 
    string CreateToken(User user, DateTime expiry); 

    // TokenValidationParameters is from Microsoft.IdentityModel.Tokens 
    TokenValidationParameters GetValidationParameters(); 
} 

I wdrożone token tworzenie za pomocą klucza zabezpieczeń RSA na JWT. Więc ...

public class RsaJwtTokenProvider : ITokenProvider 
{ 
    private RsaSecurityKey _key; 
    private string _algorithm; 
    private string _issuer; 
    private string _audience; 

    public RsaJwtTokenProvider(string issuer, string audience, string keyName) 
    { 
     var parameters = new CspParameters { KeyContainerName = keyName }; 
     var provider = new RSACryptoServiceProvider(2048, parameters); 

     _key = new RsaSecurityKey(provider); 

     _algorithm = SecurityAlgorithms.RsaSha256Signature; 
     _issuer = issuer; 
     _audience = audience; 
    } 

    public string CreateToken(User user, DateTime expiry) 
    { 
     JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); 

     ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.UserName, "jwt")); 

     // TODO: Add whatever claims the user may have... 

     SecurityToken token = tokenHandler.CreateJwtSecurityToken(new SecurityTokenDescriptor 
     { 
      Audience = _audience, 
      Issuer = _issuer, 
      SigningCredentials = new SigningCredentials(_key, _algorithm), 
      Expires = expiry.ToUniversalTime(), 
      Subject = identity 
     }); 

     return tokenHandler.WriteToken(token); 
    } 

    public TokenValidationParameters GetValidationParameters() 
    { 
     return new TokenValidationParameters 
     { 
      IssuerSigningKey = _key, 
      ValidAudience = _audience, 
      ValidIssuer = _issuer, 
      ValidateLifetime = true, 
      ClockSkew = TimeSpan.FromSeconds(0) // Identity and resource servers are the same. 
     }; 
    } 
} 

Więc teraz generujesz tokeny. Czas na ich sprawdzenie i podłączenie. Idź do swojego Startup.cs.

W ConfigureServices()

var tokenProvider = new RsaJwtTokenProvider("issuer", "audience", "mykeyname"); 
services.AddSingleton<ITokenProvider>(tokenProvider); 

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 
    .AddJwtBearer(options => { 
     options.RequireHttpsMetadata = false; 
     options.TokenValidationParameters = tokenProvider.GetValidationParameters(); 
    }); 

// This is for the [Authorize] attributes. 
services.AddAuthorization(auth => { 
    auth.DefaultPolicy = new AuthorizationPolicyBuilder() 
     .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) 
     .RequireAuthenticatedUser() 
     .Build(); 
}); 

Następnie Configure()

public void Configure(IApplicationBuilder app) 
{ 
    app.UseAuthentication(); 

    // Whatever else you're putting in here... 

    app.UseMvc(); 
} 

To powinno być o wszystko, czego potrzebujesz. Mam nadzieję, że niczego nie przeoczyłem.

Szczęśliwe wynik jest ...

[Authorize] // Yay! 
[Route("api/values")] 
public class ValuesController : Controller 
{ 
    // ... 
} 
+0

Nie myślałem nawet o rozwijaniu własnych z wszystkimi rozmowami o nowym sterowniku Core2 itd. - Myślałem, że to będzie gotowe od razu po wyjęciu z pudełka. W każdym razie jedyną rzeczą, której brakuje w twoim rozwiązaniu jest odświeżenie tokenów, ale jest to banalne, biorąc pod uwagę powyższe. Jedno pytanie - te tokeny bezpieczeństwa są nieprzejrzyste lub przejrzyste? (Czy podczas prezentacji z tokenem, stos auth nie jest chroniony i dołącza tożsamość do kontekstu webapi, czy jest to dodatkowy krok?) Dzięki Mitch! – pseabury

+0

Odszyfrowuje token i ustawia tożsamość kontekstu. W twoim kontrolerze "User.Identity.Name" będzie nazwą użytkownika, która została przekazana do JWT. – Mitch

+0

I tak, nie otrzymałem jeszcze odświeżania tokenów - działa to dość niezależnie od kodu generującego JWT. Wygeneruj token za pomocą losowego hasha, zapisz go i sprawdź podczas odświeżania połączenia. Ten kod dotyczy szybkiego interfejsu API, który musiałem przywrócić podczas fazy beta .NET Core. Jeśli ktoś ma prostszą implementację na nowszych funkcjach, byłoby świetnie. – Mitch

9

Nawiązując @Mitch odpowiedź: Auth stos zmienił trochę przeprowadzce do Rdzenia .NET 2.0. Odpowiedź poniżej to po prostu użycie nowej implementacji.

using System.Text; 
using Microsoft.AspNetCore.Authentication.JwtBearer; 
using Microsoft.AspNetCore.Builder; 
using Microsoft.AspNetCore.Hosting; 
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.DependencyInjection; 
using Microsoft.IdentityModel.Tokens; 

namespace JwtWithoutIdentity 
{ 
    public class Startup 
    { 
     public Startup(IConfiguration configuration) 
     { 
      Configuration = configuration; 
     } 

     public IConfiguration Configuration { get; } 

     // This method gets called by the runtime. Use this method to add services to the container. 
     public void ConfigureServices(IServiceCollection services) 
     { 
      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 
       .AddJwtBearer(cfg => 
       { 
        cfg.RequireHttpsMetadata = false; 
        cfg.SaveToken = true; 

        cfg.TokenValidationParameters = new TokenValidationParameters() 
        { 
         ValidIssuer = "me", 
         ValidAudience = "you", 
         IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")) //Secret 
        }; 

       }); 

      services.AddMvc(); 
     } 

     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 
     public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
     { 
      if (env.IsDevelopment()) 
      { 
       app.UseDeveloperExceptionPage(); 
      } 

      app.UseAuthentication(); 

      app.UseMvc(); 
     } 
    } 
} 

Reklamowe Controller

using System; 
using System.IdentityModel.Tokens.Jwt; 
using System.Security.Claims; 
using System.Text; 
using System.Threading.Tasks; 
using JwtWithoutIdentity.Models; 
using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Mvc; 
using Microsoft.IdentityModel.Tokens; 

namespace JwtWithoutIdentity.Controllers 
{ 
    public class TokenController : Controller 
    { 

     [AllowAnonymous] 
     [Route("api/token")] 
     [HttpPost] 
     public async Task<IActionResult> Token(LoginViewModel model) 
     { 

      if (!ModelState.IsValid) return BadRequest("Token failed to generate"); 

      var user = (model.Password == "password" && model.Username == "username"); 

      if (!user) return Unauthorized(); 

      //Add Claims 
      var claims = new[] 
      { 
       new Claim(JwtRegisteredClaimNames.UniqueName, "data"), 
       new Claim(JwtRegisteredClaimNames.Sub, "data"), 
       new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), 
      }; 

      var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("rlyaKithdrYVl6Z80ODU350md")); //Secret 
      var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); 

      var token = new JwtSecurityToken("me", 
       "you", 
       claims, 
       expires: DateTime.Now.AddMinutes(30), 
       signingCredentials: creds); 

      return Ok(new JsonWebToken() 
      { 
       access_token = new JwtSecurityTokenHandler().WriteToken(token), 
       expires_in = 600000, 
       token_type = "bearer" 
      }); 
     } 
    } 
} 

wartości Controller

using System.Collections.Generic; 
using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Mvc; 

namespace JwtWithoutIdentity.Controllers 
{ 
    [Route("api/[controller]")] 
    public class ValuesController : Controller 
    { 
     // GET api/values 
     [Authorize] 
     [HttpGet] 
     public IEnumerable<string> Get() 
     { 
      var name = User.Identity.Name; 
      var claims = User.Claims; 

      return new string[] { "value1", "value2" }; 
     } 
    } 
} 

Nadzieja to pomaga!

+2

Dziękujemy za opublikowanie tego. Szukałem czegoś takiego. Zastanawiam się, dlaczego nadal masz roszczenia i tożsamość, jeśli nie używasz 'User: Identity'. –