2017-08-16 37 views
10

Tôi nghĩ rằng tôi đã có một mục tiêu khá đơn giản trong tâm trí khi tôi đặt ra một ngày trước để thực hiện một selfer chứa auth webapi trên .NET core 2.0, nhưng tôi chưa nhận được bất cứ điều gì làm việc từ xa. Dưới đây là một danh sách những gì tôi đang cố gắng để làm:ASP.NET Core 2.0 Bearer Auth không có Identity

  • Thực hiện một thẻ vô danh bảo vệ WebAPI
  • tokens Issue & thẻ refresh từ một thiết bị đầu cuối trong cùng một dự án
  • Sử dụng [Duyệt] thuộc tính để kiểm soát tiếp cận với bề mặt api
  • Không sử dụng ASP.Net Identity (tôi có hơn xưa rất nhiều người sử dụng trọng lượng/thành viên reqs)

tôi hoàn toàn tốt với xây dựng bản sắc/yêu cầu/chính trong đăng nhập và thêm rằng để yêu cầu bối cảnh, nhưng tôi đã không nhìn thấy một ví dụ duy nhất về cách phát hành và tiêu thụ mã thông báo auth/refresh trong webapi Core 2.0 mà không có Identity. Tôi đã nhìn thấy ví dụ về MSDN 1.x của các cookie không có Identity, nhưng điều đó không giúp tôi đủ hiểu biết để đáp ứng các yêu cầu trên.

Tôi cảm thấy như đây có thể là một kịch bản phổ biến và nó không phải là khó khăn này (có thể nó không, có thể chỉ thiếu tài liệu/ví dụ?). Theo như tôi có thể nói, IdentityServer4 không tương thích với Core 2.0 Auth, opendiddict dường như yêu cầu Identity. Tôi cũng không muốn lưu trữ điểm cuối mã thông báo trong một quá trình riêng biệt, nhưng trong cùng một cá thể webapi.

Bất cứ ai có thể chỉ cho tôi một ví dụ cụ thể hay ít nhất là đưa ra một số hướng dẫn về các bước/tùy chọn tốt nhất là gì?

+0

Tôi rất thích xem mẫu này. –

+0

Bản sắc được tách riêng khỏi cơ chế JWT. Đọc [this] (https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x) và [this] (https://pioneercode.com/post/ xác thực-trong-một-asp-dot-net-core-api-phần-3-json-web-token). Lời chào hỏi. –

Trả lời

9

Đã chỉnh sửa để làm cho bản chỉnh sửa tương thích với ASP.NET Core 2.0.


Thứ nhất, một số gói NuGet:

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

Th vi một số đối tượng truyền dữ liệu cơ bản.

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

Nhập chức năng phù hợp, bạn cần có phương thức web đăng nhập/mã thông báo để gửi mã thông báo ủy quyền cho người dùng.

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

Bạn có thể nhận thấy việc tạo mã thông báo vẫn chỉ là "ma thuật" được chuyển bởi một số ITokenProvider tưởng tượng. Xác định giao diện nhà cung cấp mã thông báo.

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

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

Tôi đã triển khai tạo mã thông báo bằng khóa bảo mật RSA trên JWT. Vì vậy ...

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

Vì vậy, bây giờ bạn đang tạo mã thông báo. Thời gian để thực sự xác nhận và kết nối nó. Truy cập Startup.cs của bạn.

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

Sau đó Configure()

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

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

    app.UseMvc(); 
} 

Đó nên là về tất cả các bạn cần. Hy vọng rằng tôi đã không bỏ lỡ bất cứ điều gì.

Kết quả là hạnh phúc ...

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

Tôi thậm chí đã không thực sự nghĩ đến việc lăn của riêng tôi với tất cả các cuộc nói chuyện của stack2 auth mới Core2 - Tôi nghĩ rằng điều này sẽ được sẵn sàng thực hiện ra khỏi hộp. Trong bất kỳ trường hợp nào, điều duy nhất còn thiếu trong giải pháp của bạn là mã thông báo làm mới, nhưng điều đó là không đáng kể ở trên. Một câu hỏi - các mã thông báo bảo mật này mờ hoặc trong suốt? (ví dụ: Khi được trình bày với mã thông báo, có phải auth stack không được bảo vệ và đính kèm danh tính vào ngữ cảnh webapi hay là một bước bổ sung?) Cảm ơn Mitch! – pseabury

+0

Nó giải mã mã thông báo và thiết lập nhận dạng ngữ cảnh cho bạn. Trong bộ điều khiển của bạn, 'User.Identity.Name' sẽ là tên người dùng được chuyển vào JWT. – Mitch

+0

Và có, tôi đã không nhận được xung quanh để làm mới token - nó hoạt động khá tách biệt với mã thế hệ JWT mặc dù. Tạo mã thông báo thông qua một số băm ngẫu nhiên, lưu trữ nó và kiểm tra nó trong suốt cuộc gọi làm mới. Mã này đã cho một API nhanh chóng tôi đã phải whip lên trở lại trong giai đoạn NET lõi. Nếu ai đó có triển khai đơn giản hơn về các tính năng mới hơn, điều đó sẽ tuyệt vời. – Mitch

9

Tiếp theo @Mitch câu trả lời: Auth chồng thay đổi khá một chút chuyển sang .NET Lõi 2.0. Câu trả lời dưới đây chỉ sử dụng triển khai mới.

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

Mã điều khiển

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

Giá trị điều khiển

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

Hope this helps!

+2

Cảm ơn bạn đã đăng bài này. Đã tìm kiếm một cái gì đó như nó. Tôi chỉ tự hỏi tại sao bạn vẫn có các tuyên bố và nhận dạng nếu bạn không sử dụng 'User: Identity'. –

Các vấn đề liên quan