2017-08-16 65 views
10

我以爲我有一個非常簡單的目標,當我在前一天在.NET核心2.0上實現一個自包含的承載認證webapi時,我還沒有得到任何遠程工作。這裏是什麼,我試圖做一個列表:沒有身份的ASP.NET核心2.0承載認證

  • 實現承載令牌在同一個項目
  • 使用[授權]受保護的WebAPI
  • 發行令牌&刷新令牌從端點屬性來控制訪問API表面
  • 不使用ASP.Net身份(我有重量輕得多用戶/會員請求數)

我與建築身份/索賠/本金登錄和完全正常並補充說要請求上下文,但我還沒有看到關於如何在沒有Identity的情況下在Core 2.0 webapi中發佈和使用auth/refresh標記的示例。我已經看到了沒有Identity的1.x MSDN示例,但這並沒有讓我理解達到上述要求。

我覺得這可能是一種常見的情況,它不應該是這麼難(也許不是,也許只是缺乏文檔/示例?)。據我所知,IdentityServer4與Core 2.0 Auth不兼容,opendiddict似乎需要Identity。我也不想在單獨的進程中託管令牌端點,但在同一個webapi實例中。

任何人都可以點我一個具體的例子,或者至少給一些指導什麼最好的步驟/選項?

+0

我很想看到這樣的例子。 –

+0

身份與JWT機制分離。閱讀[this](https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x)和[this](https://pioneercode.com/post/認證功能於一個-ASP-點淨芯-API的一部分-3- JSON-web的令牌)。問候。 –

回答

9

做了編輯以使其與ASP.NET Core 2.0兼容。


首先,一些包的NuGet:

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

Th一些基本的數據傳輸對象。

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

進入正確的功能,您需要一個登錄/令牌Web方法來實際發送授權令牌給用戶。

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

您可能已經注意到,令牌創建仍然只是由一些假想的ITokenProvider傳遞過來的「魔術」。定義令牌提供者接口。

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

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

我在JWT上用RSA安全密鑰實現了令牌創建。所以...

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

所以你現在正在生成令牌。實際驗證它們並將其連接起來的時間。轉到您的Startup.cs。

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

然後Configure()

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

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

    app.UseMvc(); 
} 

這應該是所有你所需要的。希望我沒有錯過任何東西。

快樂的結果是...

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

我甚至沒有真正想過用自己的新Core2 auth stack等談論自己 - 我認爲這將是現成的開箱即用。在任何情況下,解決方案中唯一缺少的就是刷新令牌,但是鑑於上述情況,這是微不足道的。一個問題 - 這些安全令牌是不透明的還是透明的? (即當與令牌一起呈現時,auth堆棧是否取消保護並將身份附加到webapi上下文中,或者是額外的步驟?)感謝Mitch! – pseabury

+0

它解密令牌併爲您設置上下文標識。在你的控制器中,User.Identity.Name將是傳遞給JWT的用戶名。 – Mitch

+0

是的,我還沒有到處刷新令牌 - 但它與JWT代碼的運行相當獨立。通過一些隨機哈希生成令牌,存儲它,並在刷新調用期間檢查它。這段代碼是爲了一個快速的API,我不得不在.NET Core測試階段重新啓動。如果有人對新功能有更簡單的實現,那就太好了。 – Mitch

9

繼@米奇回答:驗證堆棧改變了不少移動到.NET的核心2.0。下面的答案只是使用新的實現。

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

令牌控制器

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

值控制器

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

希望這有助於!

+2

謝謝你張貼這個。正在尋找類似的東西。我只是想知道爲什麼如果你不使用'User:Identity',爲什麼你仍然有權利和身份。 –