[ Entity Frameworkコア] JJT 2を試してみてください


イントロ


今回は、最後のプロジェクトの詳細を見ます.

  • JWTについて


    JWT(JSONウェブトークン)は3つのJSON値から作られます.
    {Header}.{Payload}.{Signature}
    
    値はbase 64エンコードされます.
    例えば、“ヘッダー”はトークンタイプ名とアルゴリズム名を持ちます.
    (署名は暗号化のために直接デコードできません)

    ユーザトークンcs


    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.IdentityModel.Tokens;
    
    namespace BookshelfSample.Users;
    
    public class UserTokens: IUserTokens
    {
    ...
        public string GenerateToken(ApplicationUser user)
        {
            return new JwtSecurityTokenHandler()
                .WriteToken(new JwtSecurityToken(this.config["Jwt:Issuer"],
                    this.config["Jwt:Audience"],
                    claims: new []
                    {
                        new Claim(ClaimTypes.Email, user.Email)
                    },
                    expires: DateTime.Now.AddSeconds(30),
                    signingCredentials: new SigningCredentials(
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Jwt:Key"])),
                        SecurityAlgorithms.HmacSha256)));
        }
        public IEnumerable<string> DecodeToken(string value)
        {
            foreach(var t in value.Split("."))
            {
                yield return Base64UrlEncoder.Decode(t);
            }
        }
    }
    

    ApplicationUserService。cs


    ...
        public async Task<UserActionResult> SigninAsync(SigninValue value, ISession session)
        {
    
            var target = await this.users.GetByEmailForSigninAsync(value.Email);
            if(target == null)
            {
                return ActionResultFactory.GetFailed("Invalid e-mail or password");
            }
            var result = await this.signInManager.PasswordSignInAsync(target, value.Password, false, false);
            if(result.Succeeded)
            {
                var token = this.userTokens.GenerateToken(target);
                session.SetString("user-token", token);
    
                logger.LogDebug($"Token: {token}");
                foreach(var t in this.userTokens.DecodeToken(token))
                {
                    logger.LogDebug($"Decoded: {t}");
                }            
                return ActionResultFactory.GetSucceeded();
            }
            return ActionResultFactory.GetFailed("Invalid e-mail or password");
        }
    ...
    

    結果


    Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJleGFtcGxlQG1haWwuY29tIiwiZXhwIjoxNjQ4MzEzMDY0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUxMTAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUxMTAifQ.AtzZ-RnSNG3SRLEsNEC3XI7OjiQ8Y8YWpRdBCTI5vNY
    Decoded: {"alg":"HS256","typ":"JWT"}
    Decoded: {"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress":"[email protected]","exp":1648397836,"iss":"http://localhost:5110","aud":"http://localhost:5110"}
    Decoded: ♀?▼?Q?   ←|?☻?L????YGn?Y~?$?1???
    
    の仕様を読むことができますHeader and Claims RFC 7519で.

    認証


    JWTのベアラ認証のために、私はAuthenticationBuilderにオプションを“addjwtearer”を追加しました.

    プログラム。cs


    ...
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = builder.Configuration["Jwt:Issuer"],
                    ValidAudience = builder.Configuration["Jwt:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
                };
            });
    ...
    
    “validate ~”プロパティ“false”の全てを設定すると、常に同じトークン値を使用できますか?
    答えはイエスです.

    プログラム。cs


    ...
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateTokenReplay = false,
                    ValidateActor = false,
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = false,
                    ValidIssuer = builder.Configuration["Jwt:Issuer"],
                    ValidAudience = builder.Configuration["Jwt:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
                };
            });
    ...
        app.Use(async (context, next) =>
        {
            var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJleGFtcGxlQG1haWwuY29tIiwiZXhwIjoxNjQ4MzEzMDY0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUxMTAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUxMTAifQ.AtzZ-RnSNG3SRLEsNEC3XI7OjiQ8Y8YWpRdBCTI5vNY";
            context.Request.Headers.Add("Authorization", $"Bearer {token}");
            await next();
        });
    ...
    
    セットトークンはセキュリティキーで暗号化されているので、“issuersigningkey”を省略できません.

    書き込みトークン


    次にJWTの作成を見ます.

    ユーザトークンcs


    ...
        public string GenerateToken(ApplicationUser user)
        {
            return new JwtSecurityTokenHandler()
                .WriteToken(new JwtSecurityToken(this.config["Jwt:Issuer"],
                    this.config["Jwt:Audience"],
                    claims: new []
                    {
                        new Claim(ClaimTypes.Email, user.Email)
                    },
                    expires: DateTime.Now.AddSeconds(30),
                    signingCredentials: new SigningCredentials(
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this.config["Jwt:Key"])),
                        SecurityAlgorithms.HmacSha256)));
        }
    ...
    
    JwtSecurityTokenには5オーバーロードがあります.しかし、JWTheader、JWtPayload、およびJwtSecurityTokenを引数として受け取りません.
    jwtSecurityToken.csは、JwtSecurityTokenコンストラクターの他の引数からSigningCredentialsとJWtPayloadからJWTheaderを作成します.
    ヘッダとペイロードセクションはbase 64エンコードされたJSONです.
    しかし、署名セクションはいくつかのBase 64符号化された値から作られます.
    だから私は直接デコードすることはできません.
  • JwtSecurityToken.cs - GitHub
  • JwtSecurityTokenHandler.cs - GitHub
  • JwtHeader.cs - GitHub
  • JwtPayload.cs - GitHub
  • JwtTokenUtilities.cs - GitHub
  • セキュリティキー


    セキュリティキーには特別な要件はありません.
    しかし、HMAC SHA 256を使用するので、キー値は少なくとも16ビット長を持たなければなりません.
    キーが16ビット未満であるならば、私は実行時例外を得ます.
    The encryption algorithm 'System.String' requires a key size of at least 'System.Int32' bits. Key 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey', is of size: 'System.Int32'. (Parameter 'key')
    ...
    
    私はアルファベットや数字に加えて、シンボル、日本語などを使用することができます.
    キーが長か短いかどうか、結果は256ビット長です.

    資源

  • RFC 7516 - JSON Web Encryption (JWE)
  • RFC 7519 - JSON Web Token (JWT)
  • RFC7521 - Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants
  • RFC7523 - JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
  • OAuth徹底入門 セキュアな認可システムを適用するための原則と実践(OAuth2 in Action)
  • JwtBearerExtensions.AddJwtBearer Method - Microsoft Docs
  • Microsoft.IdentityModel.JsonWebTokens - GitHub
  • Secure Hash Standard - NIST