[解決済み】ASP.NET Web APIのJWT認証について
質問
Web APIアプリケーションでJWTベアラートークン(JSON Web Token)をサポートしようとしているのですが、迷っています。
.NET CoreとOWINアプリケーションのサポートが表示されます。
現在、IISでアプリケーションをホストしています。
私のアプリケーションでこの認証モジュールを実現するにはどうしたらよいでしょうか?また
<authentication>
の構成は、フォーム/Windows認証と同じようなものですか?
解決方法は?
この質問に回答しました。 ASP.NET Web APIのセキュリティを確保する方法 4年前にHMACを使用。
今、セキュリティはいろいろと変わってきていて、特にJWTが流行ってきていますね。この回答では、OWIN、Oauth2、ASP.NET Identityなどのジャングルから迷子にならないよう、JWTの使い方をできるだけシンプルで基本的な方法で説明したいと思います :)
JWTトークンのことを知らない人は、こちらをご覧ください。
https://www.rfc-editor.org/rfc/rfc7519
基本的に、JWTトークンは以下のようなものです。
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
JWTトークンには3つのセクションがあります。
- ヘッダーです。Base64でエンコードされたJSON形式
- クレーム。Base64でエンコードされたJSON形式。
- 署名(Signature)。HeaderとClaimsを元に作成され、Base64でエンコードされた署名。
ウェブサイトを利用する場合 jwt.io を入力すると、トークンがデコードされ、以下のように表示されます。
技術的には、JWTはヘッダとクレームから、ヘッダで指定されたセキュリティアルゴリズム(例:HMACSHA256)で署名されたものを使用しています。したがって、JWTのクレームに機密情報を格納する場合は、HTTPで転送する必要があります。
さて、JWT認証を利用するためには、レガシーなWebApiシステムであれば、OWINミドルウェアは特に必要ないのです。JWTトークンをどのように提供し、リクエストが来たときにそのトークンをどのように検証するかというシンプルなコンセプトです。それだけです。
には
作成したデモ(github)
JWTトークンを軽量化するために、JWTトークンは
username
と
expiration time
. しかしこの方法では、ロール認証を行いたい場合など、ロールなどの情報を追加するために新しいローカルID(プリンシパル)を再構築する必要があります。しかし、JWTにもっと情報を追加したいのであれば、それはあなた次第です:それは非常に柔軟です。
OWINミドルウェアを使用する代わりに、コントローラのアクションを使用して、JWTトークンのエンドポイントを提供することができます。
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
本番環境では、POSTリクエストまたはBasic Authenticationエンドポイントを使用して、JWTトークンを提供する必要があります。
を元にトークンを生成する方法
username
?
という NuGet パッケージを使用することができます。
System.IdentityModel.Tokens.Jwt
を使用してトークンを生成することができます。このデモでは
HMACSHA256
と
SymmetricKey
:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
JWTトークンを提供するエンドポイントが完了しました。
リクエストが来たときにJWTを検証するには?
で
デモ
を構築しました。
JwtAuthenticationAttribute
を継承している
IAuthenticationFilter
(の認証フィルタについて詳しく説明します。
ここで
).
この属性を使えば、どんなアクションでも認証することができます:そのアクションにこの属性をつけるだけです。
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
WebAPIに来るすべてのリクエストを検証したい場合は、OWINミドルウェアやDelegateHanderを使用することもできます(ControllerやActionに限定されません)。
以下は、認証フィルタのコアとなるメソッドです。
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null || !identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
ワークフローとしては、JWTライブラリ(上記のNuGetパッケージ)を使ってJWTトークンを検証し、その結果を返します。
ClaimsPrincipal
. 必要であれば、ユーザーがシステムに存在するかどうかの確認や、その他のカスタムバリデーションを追加することもできます。
JWTトークンを検証し、プリンシパルを取得するためのコードです。
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
JWTトークンが検証され、プリンシパルが返された場合、新しいローカルIDを構築し、そこにさらに情報を入れて、ロール認証をチェックする必要があります。
を忘れずに追加してください。
config.Filters.Add(new AuthorizeAttribute());
(デフォルトの認可) をグローバルスコープで使用することで、リソースへの匿名リクエストを防止します。
Postman を使って デモ :
リクエストトークン(上に書いたように、デモのためだけの素朴なもの)。
GET http://localhost:{port}/api/token?username=cuong&password=1
認可されたリクエストのヘッダーにJWTトークンを入れる。
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
デモはこちらでご覧いただけます。 https://github.com/cuongle/WebApi.Jwt
関連
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み】Microsoft.Extensions.LoggingからILoggerを解決することができない
-
[解決済み】WebResource.axdとは何ですか?
-
[解決済み] なぜパスワードにはStringではなくchar[]が好まれるのですか?
-
[解決済み] Chromeを使用してASP.NET Web APIがXMLの代わりにJSONを返すようにするにはどうすればよいですか?
-
[解決済み] Windowsフォームアプリケーションで、アプリケーションの設定を保存するにはどうすればよいですか?
-
[解決済み] JWT(JSONウェブトークン)の有効期限を自動的に延長する機能
-
[解決済み] 認証とセッション管理に関するSPAのベストプラクティス
-
[解決済み】JWTをブラウザに保存する場所は?CSRFから保護する方法は?
-
[解決済み] ASP.NET Coreのトークンベース認証
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み】統合マネージドパイプラインモードで適用されないASP.NETの設定が検出された
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】プロジェクトビルド時のエラー。エディタでスクリプトにコンパイルエラーがあるため、Playerのビルドにエラーが発生する
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】Socket.Selectがエラー "An operation was attempted on something that is not a socket" を返す。
-
[解決済み】"指定されたパスのフォーマットはサポートされていません。"
-
[解決済み】WSACancelBlockingCallの例外について
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み】インデックスが範囲外でした。コレクションパラメータname:indexのサイズより小さく、非負でなければなりません。