.NET Core2.0上のJWT


83

私は、JWTをDotNetコア2.0で動作させるためにかなりの冒険をしてきました(現在、本日最終リリースに到達しています)。あるトンのドキュメントのは、しかし、すべてのサンプルコードでは、コアに新鮮で推奨されないAPIを使用して来ているように見える、積極的に実装されることになっています正確にどのように把握することを目のくらむようです。ホセを使ってみましたが、アプリです。UseJwtBearerAuthenticationは非推奨になり、次に何をすべきかについてのドキュメントはありません。

承認ヘッダーからJWTを解析し、HS256でエンコードされたJWTトークンのリクエストを承認できるdotnet core 2.0を使用するオープンソースプロジェクトを持っている人はいますか?

以下のクラスは例外をスローしませんが、リクエストは許可されておらず、なぜ許可されていないのわかりません。応答は空の401なので、例外はなかったが、秘密が一致していないことを示しています。

奇妙なことに、トークンはHS256アルゴリズムで暗号化されていますが、どこでもそのアルゴリズムを使用するように強制するように指示するインジケーターが表示されません。

これが私がこれまでに持っているクラスです:

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace Site.Authorization
{
    public static class SiteAuthorizationExtensions
    {
        public static IServiceCollection AddSiteAuthorization(this IServiceCollection services)
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SECRET_KEY"));

            var tokenValidationParameters = new TokenValidationParameters
            {
                // The signing key must match!
                ValidateIssuerSigningKey = true,
                ValidateAudience = false,
                ValidateIssuer = false,
                IssuerSigningKeys = new List<SecurityKey>{ signingKey },


                // Validate the token expiry
                ValidateLifetime = true,
            };

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


            })

            .AddJwtBearer(o =>
            {
                o.IncludeErrorDetails = true;
                o.TokenValidationParameters  = tokenValidationParameters;
                o.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = c =>
                    {
                        c.NoResult();

                        c.Response.StatusCode = 401;
                        c.Response.ContentType = "text/plain";

                        return c.Response.WriteAsync(c.Exception.ToString());
                    }

                };
            });

            return services;
        }
    }
}

[承認] 401を使用したすべてのリクエストを変更せずに検証の署名をオフにしました
Michael Draper

2
完全なコードを投稿できますか?または私はあなたが仕事にこれを得たか見てみたいデモプロジェクト...
ピョートルStulinski


あなたを助けることができる別の投稿があります。stackoverflow.com/a/48295906/8417618
マルコバルベーロ

回答:


87

これは、コントローラーを使用した完全に機能する最小限のサンプルです。PostmanまたはJavaScript呼び出しを使用して確認できることを願っています。

  1. appsettings.json、appsettings.Development.json。セクションを追加します。キーはかなり長くする必要があり、発行者はサービスのアドレスであることに注意してください。

    ...
    ,"Tokens": {
        "Key": "Rather_very_long_key",
        "Issuer": "http://localhost:56268/"
    }
    ...
    

    !!! 実際のプロジェクトでは、キーをappsettings.jsonファイルに保持しないでください。これは環境変数に保持し、次のようにする必要があります。

    Environment.GetEnvironmentVariable("JWT_KEY");
    

更新:.netコア設定がどのように機能するかを確認すると、Environmentから正確に取得する必要はありません。設定を使用できます。ただし、代わりに、この変数を本番環境の環境変数に書き込む場合があります。その場合、コードは構成ではなく環境変数を優先します。

  1. AuthRequest.cs:ログインとパスワードを渡すための値を保持するDto:

    public class AuthRequest
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
    
  2. app.UseMvc()の前のConfigure()メソッドのStartup.cs:

    app.UseAuthentication();
    
  3. ConfigureServices()のStartup.cs:

    services.AddAuthentication()
        .AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
    
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidIssuer = Configuration["Tokens:Issuer"],
                ValidAudience = Configuration["Tokens:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
            };
    
        });
    
  4. コントローラを追加します。

        [Route("api/[controller]")]
        public class TokenController : Controller
        {
            private readonly IConfiguration _config;
            private readonly IUserManager _userManager;
    
            public TokenController(IConfiguration configuration, IUserManager userManager)
            {
                _config = configuration;
                _userManager = userManager;
            }
    
            [HttpPost("")]
            [AllowAnonymous]
            public IActionResult Login([FromBody] AuthRequest authUserRequest)
            {
                var user = _userManager.FindByEmail(model.UserName);
    
                if (user != null)
                {
                    var checkPwd = _signInManager.CheckPasswordSignIn(user, model.authUserRequest);
                    if (checkPwd)
                    {
                        var claims = new[]
                        {
                            new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
                            new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                        };
    
                        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
                        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                        var token = new JwtSecurityToken(_config["Tokens:Issuer"],
                        _config["Tokens:Issuer"],
                        claims,
                        expires: DateTime.Now.AddMinutes(30),
                        signingCredentials: creds);
    
                        return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
                    }
                }
    
                return BadRequest("Could not create token");
            }}
    

それはすべての人々です!乾杯!

更新:人々は現在のユーザーを取得する方法を尋ねます。Todo:

  1. ConfigureServices()のStartup.csに追加

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. コントローラーでコンストラクターに追加します。

    private readonly int _currentUser;
    public MyController(IHttpContextAccessor httpContextAccessor)
    {
       _currentUser = httpContextAccessor.CurrentUser();
    }
    
  3. どこかに拡張機能を追加し、コントローラーで使用します(...を使用)

    public static class IHttpContextAccessorExtension
    {
        public static int CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {
            var stringId = httpContextAccessor?.HttpContext?.User?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
            int.TryParse(stringId ?? "0", out int userId);
    
            return userId;
        }
    }
    

1
これは私にとって非常に役に立ちました。私がまだはっきりしていない唯一のことは、後続の呼び出しのためにトークンをチェックする方法、または現在ログインしているユーザーが誰であるかを判断する方法です。
Travesty3 2017年

本当に簡単です。コントローラのメソッド呼び出しで:var currentUser = HttpContext.User.Identity.Name; 乾杯!
alerya 2017年

1
おい、これはとても大きな助けでした、詳細な答えをありがとうございました!
ライアンマン2017年

1
以下の情報をありがとう。401Unauthorizedエラーから私を救いました。3.app.UseMvc()の前のConfigure()メソッドのStartup.cs:app.UseAuthentication();
スミア2018

1
これは矛盾しています。キーをappsettingsファイルに保存するべきではありませんが、ここで取得していますConfiguration ["Tokens:Key"])。キーを安全に取得する別のサービスがある場合、それをConfigureServicesにどのように組み込むのでしょうか。
ウォーグレイビー

18

tokenValidationParameters彼らがこのように見えるときの私の作品:

 var tokenValidationParameters = new TokenValidationParameters
  {
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = GetSignInKey(),
      ValidateIssuer = true,
      ValidIssuer = GetIssuer(),
      ValidateAudience = true,
      ValidAudience = GetAudience(),
      ValidateLifetime = true,
      ClockSkew = TimeSpan.Zero
   };

そして

    static private SymmetricSecurityKey GetSignInKey()
    {
        const string secretKey = "very_long_very_secret_secret";
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));

        return signingKey;
    }

    static private string GetIssuer()
    {
        return "issuer";
    }

    static private string GetAudience()
    {
        return "audience";
    }

さらに、次のようにoptions.RequireHttpsMetadata = falseを追加します。

         .AddJwtBearer(options =>
       {         
           options.TokenValidationParameters =tokenValidationParameters         
           options.RequireHttpsMetadata = false;
       });

編集

電話することを忘れないでください

 app.UseAuthentication();

Startup.cs-> app.UseMvc();の前にメソッドを構成します。


私はそれがapp.UseAuthentication();だと思います。トリックを行うコール、私はそれが必要だとは知りませんでした。ありがとうございました!
Michael Draper

ValidAudience、ValidIssuer、IssuerSigningKeyも指定する必要があると思います。それは私のためにそれなしでは動作しませんでした
エイドリアンKsiężarczykに

はい、それはまさにそれでした。app.UseAuthentication()を追加する必要があり、それだけで済みました。どうもありがとうございました!
Michael Draper

3
トークンが正常に承認された場合でも、app.UseAuthentication();前に呼び出されたノートの+1app.UseMvc();は、401を取得します。私は、その1つを処理するのに約2日を費やしました。
pcdev 2017

1
"app.UseAuthentication();" 、. netコアを1.0から2.0にアップグレードした後、401の問題を修正するために丸一日を費やしましたが、この投稿が表示されるまで解決策が見つかりませんでした。エイドリアンに感謝します。
チャン

8

WebApiデモを使用したAsp.netCore 2.0JWTベアラートークン認証の実装

パッケージ「Microsoft.AspNetCore.Authentication.JwtBearer」を追加します

Startup.cs ConfigureServices()

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

            });

Startup.cs Configure()

// ===== Use Authentication ======
        app.UseAuthentication();

User.cs //たとえばモデルクラスです。何でもかまいません。

public class User
{
    public Int32 Id { get; set; }
    public string Username { get; set; }
    public string Country { get; set; }
    public string Password { get; set; }
}

UserContext.cs //これは単なるコンテキストクラスです。何でもかまいません。

public class UserContext : DbContext
{
    public UserContext(DbContextOptions<UserContext> options) : base(options)
    {
        this.Database.EnsureCreated();
    }

    public DbSet<User> Users { get; set; }
}

AccountController.cs

[Route("[controller]")]
public class AccountController : Controller
{

    private readonly UserContext _context;

    public AccountController(UserContext context)
    {
        _context = context;
    }

    [AllowAnonymous]
    [Route("api/token")]
    [HttpPost]
    public async Task<IActionResult> Token([FromBody]User user)
    {
        if (!ModelState.IsValid) return BadRequest("Token failed to generate");
        var userIdentified = _context.Users.FirstOrDefault(u => u.Username == user.Username);
            if (userIdentified == null)
            {
                return Unauthorized();
            }
            user = userIdentified;

        //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
        {
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = DateTime.Now.AddMinutes(30),
            token_type = "bearer"
        });
    }
}

UserController.cs

[Authorize]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly UserContext _context;

    public UserController(UserContext context)
    {
        _context = context;
        if(_context.Users.Count() == 0 )
        {
            _context.Users.Add(new User { Id = 0, Username = "Abdul Hameed Abdul Sattar", Country = "Indian", Password = "123456" });
            _context.SaveChanges();
        }
    }

    [HttpGet("[action]")]
    public IEnumerable<User> GetList()
    {
        return _context.Users.ToList();
    }

    [HttpGet("[action]/{id}", Name = "GetUser")]
    public IActionResult GetById(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if(user == null)
        {
            return NotFound();
        }
        return new ObjectResult(user);
    }


    [HttpPost("[action]")]
    public IActionResult Create([FromBody] User user)
    {
        if(user == null)
        {
            return BadRequest();
        }

        _context.Users.Add(user);
        _context.SaveChanges();

        return CreatedAtRoute("GetUser", new { id = user.Id }, user);

    }

    [HttpPut("[action]/{id}")]
    public IActionResult Update(long id, [FromBody] User user)
    {
        if (user == null)
        {
            return BadRequest();
        }

        var userIdentified = _context.Users.FirstOrDefault(u => u.Id == id);
        if (userIdentified == null)
        {
            return NotFound();
        }

        userIdentified.Country = user.Country;
        userIdentified.Username = user.Username;

        _context.Users.Update(userIdentified);
        _context.SaveChanges();
        return new NoContentResult();
    }


    [HttpDelete("[action]/{id}")]
    public IActionResult Delete(long id)
    {
        var user = _context.Users.FirstOrDefault(u => u.Id == id);
        if (user == null)
        {
            return NotFound();
        }

        _context.Users.Remove(user);
        _context.SaveChanges();

        return new NoContentResult();
    }
}

PostManでのテスト: それに応じてトークンを受け取ります。

他のWebサービスのヘッダーでTokenTypeとAccessTokenを渡します。 ここに画像の説明を入力してください

幸運を祈ります!私は初心者です。asp.netコアの学習を開始するのに1週間しか費やしませんでした。


「AccountController」をアクティブ化しようとしているときに、InvalidOperationException:タイプ「WebApplication8.UserContext」のサービスを解決できません。郵便配達員がPostto account / api / tokenに電話をかけようとすると
Kirsten Greed 2018年

7

これがあなたのための解決策です。

startup.csで、まず、サービスとして構成します。

  services.AddAuthentication().AddJwtBearer(cfg =>
        {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = "somethong",
                ValidAudience = "something",
                :
            };
        });

次に、configでこのサービスを呼び出します

          app.UseAuthentication();

これで、属性を追加することでコントローラーで使用できます

          [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
          [HttpGet]
          public IActionResult GetUserInfo()
          {

Frond-endとしてangularを使用するソースコードの詳細については、こちらを参照してください


これが私のベーコンを救った答えでした![承認]を使用できると便利です。これがStartup.csで処理できると想像してください
Simon

1
Simon、なぜなら、services.AddAuthentication()。AddCookie()。AddJwtBearer();のように、同じasp.netコアMVCアプリケーションに複数のスキームを含めることができるからです。
ロングフィールド

1
services.AddAuthorization起動時の機能でデフォルトの認証方式を設定することもできます。
Neville Nazerane 2017

@NevilleNazeraneのステートメントをフォローアップするために、デフォルトの認証スキーム(プレーンな[Authorize]デコレータで使用される)を設定するコードがこの質問に答えています。それのservices.AddAuthentication(sharedOptions => {sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;})
Ryanman

IssuerSigningKeyについてこの例に従おうとすると、エラーが発生します。ソースタイプ「文字列」をターゲットタイプ「Microsoft.IdentityModel.Tokens.SecurityKey」に変換できません
Kirsten Greed

4

.Net Core 2.0APIの実装は次のとおりです。

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(
        config =>
        {
            // This enables the AuthorizeFilter on all endpoints
            var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
            
        }
        ).AddJsonOptions(opt =>
        {
            opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
        });

        services.AddLogging();

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Audience = Configuration["AzureAD:Audience"];  
            options.Authority = Configuration["AzureAD:AADInstance"] + Configuration["AzureAD:TenantId"];
        });            
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication(); // THIS METHOD MUST COME BEFORE UseMvc...() !!
        app.UseMvcWithDefaultRoute();            
    }

appsettings.json:

{
  "AzureAD": {
    "AADInstance": "https://login.microsoftonline.com/",
    "Audience": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Domain": "mydomain.com",
    "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
  ...
}

上記のコードは、すべてのコントローラーで認証を有効にします。匿名アクセスを許可するには、コントローラー全体を装飾できます。

[Route("api/[controller]")]
[AllowAnonymous]
public class AnonymousController : Controller
{
    ...
}

または、メソッドを装飾して単一のエンドポイントを許可します。

    [AllowAnonymous]
    [HttpPost("anonymousmethod")]
    public async Task<IActionResult> MyAnonymousMethod()
    {
        ...
    }

ノート:

  • これはAD認証での私の最初の試みです-何か問題がある場合は、私に知らせてください!

  • Audienceクライアントによって要求されたリソースIDと一致する必要があります。この場合、クライアント(Angular Webアプリ)はAzure ADに個別に登録され、APIでオーディエンスとして登録したクライアントIDを使用していました。

  • ClientIdAzure PortalではアプリケーションIDと呼ばれ(なぜ??)、APIのアプリ登録のアプリケーションIDです。

  • TenantIdAzure Active Directory>プロパティの下にあるAzureポータル(なぜ??)ではディレクトリIDと呼ばれます

  • APIをAzureがホストするWebアプリとしてデプロイする場合は、必ずアプリケーション設定を設定してください。

    例えば。AzureAD:オーディエンス/ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx


3

@aleryaによる優れた回答を更新するために、ヘルパークラスを次のように変更する必要がありました。

public static class IHttpContextAccessorExtension
    {
        public static string CurrentUser(this IHttpContextAccessor httpContextAccessor)
        {           
            var userId = httpContextAccessor?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
            return userId;
        }
    }

次に、サービスレイヤーでuserIdを取得できます。私はそれがコントローラーで簡単であることを知っています、しかしさらに下の挑戦。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.