[Microsoft] ASP.NET CoreでJWT認証するそこそこシンプルなサンプル(統合テスト付き)
これはなに?
ASP.NET Coreにおいて、JWTトークンを発行してもらうコードと、JWTトークンを検証するコードのサンプルです。
dotnetのバージョンは、すでにサポート切れの2.2です。
たぶん3.1でもそんなに変わらないはずです。
統合テストも作っています。
サンプルプログラム
プログラム全体はこちらに置いています。
https://github.com/sengokyu/Ex.JwtAuth
git clone
してdotnet test ExJwtAuth.Tests
としてもらえれば、テストが動きます。
JWTトークンを発行してもらうコード
ログインIDとパスワードを受け付けて、JWTトークンをJSONとして返しています。
ログインIDとパスワードを受け付けていますが、何もしておらず素通しです。
JWTトークンは、System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler
クラスを使用すると生成できます。
(サンプルは、すべてをASP.NET MVCのコントローラの中でやっています。いわゆるファットコントローラというやつです。よいこは真似してはダメです)。
ExJwtAuth/Controllers/AuthenticationController.cs
[AllowAnonymous]
[HttpPost]
public IActionResult Login(
[Required][FromBody]LoginParam loginParam
)
{
var handler = new JwtSecurityTokenHandler();
// JWT内に入れるクレームです。
var claims = new[] {
new Claim(ClaimTypes.Name, loginParam.Username)
};
var subject = new ClaimsIdentity(claims);
var credentials = new SigningCredentials(
JwtSecurityConfiguration.SecurityKey,
SecurityAlgorithms.HmacSha256);
// ここでトークンを生成しています。
var token = handler.CreateJwtSecurityToken(
audience: JwtSecurityConfiguration.Audience,
issuer: JwtSecurityConfiguration.Issuer,
subject: subject,
signingCredentials: credentials);
var tokenText = handler.WriteToken(token);
var result = new
{
token = tokenText
};
return Ok(result);
}
JWTトークンを検証するコード
JWTトークンの検証は、あらかじめ用意されている機能を追加するだけです。
Startup
クラスの中で有効化します。
AddJwtBearer
メソッドに渡すオプションは、また別途DIするようにしています。
ExJwtAuth/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions: null);
// 省略
// JwtBearerOptionsの設定は別クラスでやる
services
.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerConfigureOptions>();
}
追加した機能を有効にします。
Useする順番大事です。
ExJwtAuth/Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app
.UseAuthentication()
.UseMvc();
}
JWTを検証する設定
Webサーバに渡されたJWTを検証する設定は別クラスにしました。
AudienceもIssurerも検証するようにしています。
Audienceの検証は、手抜きして何が来てもOKなようにしています。
実際はデータベースを検索したりします。
Configurations/JwtBearerConfigureOptions.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace ExJwtAuth.Configuratins
{
public class JwtBearerConfigureOptions : IConfigureNamedOptions<JwtBearerOptions>
{
public void Configure(string name, JwtBearerOptions options)
{
if (name != JwtBearerDefaults.AuthenticationScheme)
{
return;
}
options.TokenValidationParameters = new TokenValidationParameters
{
AudienceValidator = this.AudienceValidatorDelegate,
ValidIssuer = JwtSecurityConfiguration.Issuer,
IssuerSigningKey = JwtSecurityConfiguration.SecurityKey,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
};
}
public void Configure(JwtBearerOptions options)
{
Configure(JwtBearerDefaults.AuthenticationScheme, options);
}
public bool AudienceValidatorDelegate(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
// 実際は、データベースを見たりする。
return true;
}
}
}
ログイン状態を検証するコード
ログイン状態を検証するために、ログインしているユーザ名をJSONで返すコードを追加しました。
ExJwtAuth/Controllers/AuthenticatinController.cs
[Authorize()]
[HttpGet]
public IActionResult GetAction()
{
var username = HttpContext.User.Identity.Name;
var result = new
{
username = username
};
return Ok(result);
}
統合テスト
統合テストでは、以下の内容をテストしています。
- JWTトークン無しでアクセスして HTTP 401 が返ること
- ログインID/パスワードをPOSTしてJWTトークンが返ること
- JWTトークン付きでアクセスしてログインIDが返ること
テストのアサーションには、FluentAssertionsを使用しています。Assert.Equal(期待値, 実際の値)
みたいに書くよりは、書きやすく読みやすいのではないでしょうか。
テストクラスの雛形
テストクラスはIClassFixture
を実装します。xUnitテストランナーに対して、テスト間で情報を持ちまわるようにすることを指示しています。
コンストラクタでWebApplicationFactory
クラスを受け取ります。このクラスを使ってHTTPクライアントを生成します。
public class AuthenticationApiTests
: IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> factory;
public AuthenticationApiTests(WebApplicationFactory<Startup> factory)
{
this.factory = factory;
}
}
JWTトークン無しでアクセスして HTTP 401 が返ることを確認するテスト
[Fact]
をつけたものが、xUnitに認識されて実行されます。
HTTP Clientを作って、トップページへアクセスし、HTTPステータスコードを確認しています。
[Fact]
public async Task Test未認証だと401()
{
// Given
var uri = "/";
using (var client = factory.CreateClient())
{
// When
var response = await client.GetAsync(uri);
// Then
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
}
その他のテスト
長くなるので省略します。githubを見てください。
https://github.com/sengokyu/Ex.JwtAuth/blob/master/ExJwtAuth.Tests/AuthenticationApiTests.cs
JWT認証に必要なパッケージ
以下のパッケージが追加で必要でした。
リンク
- JSON Web Token(JWT)のClaimについて JWTに入れるクレームの解説があります。
おまけ
テストクラスのテストメソッド名を日本語にするのは、結構アリだと思うんですけどどうでしょう?