2017-08-16 30 views
10

.NETコア2.0で自己完結型のベアラ認証のwebapiを実装するために前日に設定したとき、私はかなり単純な目標を持っていたと思っていましたが、リモートで動作するものここで私は何をしようとしているのリストです:アイデンティティのないASP.NETコア2.0ベアラ認証

  • は、同じプロジェクト内のエンドポイントからの
  • 発行トークン&リフレッシュトークン
  • を制御するための[承認]属性を使用して、ベアラトークン保護WEBAPIを実装しますAPIの表面へのアクセス
  • 私は、ログインしてアイデンティティ/請求/プリンシパルを構築すると完全に大丈夫だよASP.Netアイデンティティ(私は非常に軽量のユーザー/メンバーシップREQSを持っている)

を使用しないと、コンテキストを要求するためにそれを追加しましたが、アイデンティティのないCore 2.0 webapiで認証/更新トークンを発行して消費する方法の一例を見ていません。私はアイデンティティのないクッキーの1.x MSDNの例を見てきましたが、上記の要件を満たすために十分理解していませんでした。

私はこれが一般的なシナリオかもしれないと感じています。これは難しいことではないでしょう(おそらく、ドキュメンテーション/例がないかもしれません)。私が知る限り、IdentityServer4はCore 2.0 Authと互換性がありません。opendiddictはIdentityを必要とするようです。トークンエンドポイントを別のプロセスでホストするのではなく、同じwebapiインスタンス内にホストすることも望みます。

誰かが私に具体的な例を教えてもらえますか、あるいは少なくとも、最良のステップ/オプションが何であるかについての指針を与えることはできますか?

+0

私もこのサンプルを見たいと思います。 –

+0

それはJWTメカニズムから切り離されていると識別します。 [こちら](https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x)と[this](https://pioneercode.com/post/)をお読みください。 asp-dot-net-core-api-part-3-json-web-token)を使用します。ご挨拶 –

答えて

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

。うまくいけば私は何かを逃さなかった。

幸せな結果が@Mitch答えに続き...

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

私は新しいCore2認証スタックなどの話で自分自身を圧倒することさえ考えていませんでした。私はこれが箱からすぐに作れると思いました。いずれにしても、あなたの解決策から欠けているのは、リフレッシュ・トークンだけですが、それは上記の点では些細なことです。 1つの質問 - これらのセキュリティトークンは不透明または透明ですか? (つまり、トークンが提示されたとき、authスタックは保護されず、アイデンティティをwebapiコンテキストに付加するのですか、それとも追加のステップですか?)Thanks Mitch! – pseabury

+0

トークンを復号化し、コンテキストアイデンティティを設定します。あなたのコントローラでは、 'User.Identity.Name'はJWTに渡されたユーザ名になります。 – Mitch

+0

そして、はい、まだトークンをリフレッシュするつもりはありません - それはJWT生成コードとはかなり離れています。いくつかのランダムなハッシュでトークンを生成し、保存し、リフレッシュコール中にチェックします。このコードは、.NET Coreベータ版段階で取り上げなければならなかったクイックAPI用です。誰かが新しい機能でより簡単な実装を持っているなら、それは素晴らしいでしょう。 – 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'を使用していない場合でもなぜあなたはまだクレームとアイデンティティを持っているのだろうと思っています。 –

関連する問題