1. ホーム
  2. c#

[解決済み】ASP.NET Web APIのJWT認証について

2022-03-26 02:33:39

質問

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つのセクションがあります。

  1. ヘッダーです。Base64でエンコードされたJSON形式
  2. クレーム。Base64でエンコードされたJSON形式。
  3. 署名(Signature)。HeaderとClaimsを元に作成され、Base64でエンコードされた署名。

ウェブサイトを利用する場合 jwt.io を入力すると、トークンがデコードされ、以下のように表示されます。

技術的には、JWTはヘッダとクレームから、ヘッダで指定されたセキュリティアルゴリズム(例:HMACSHA256)で署名されたものを使用しています。したがって、JWTのクレームに機密情報を格納する場合は、HTTPで転送する必要があります。

さて、JWT認証を利用するためには、レガシーなWebApiシステムであれば、OWINミドルウェアは特に必要ないのです。JWTトークンをどのように提供し、リクエストが来たときにそのトークンをどのように検証するかというシンプルなコンセプトです。それだけです。

には 作成したデモ(github) JWTトークンを軽量化するために、JWTトークンは usernameexpiration 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 を使用してトークンを生成することができます。このデモでは HMACSHA256SymmetricKey :

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