인증서버 - JWT 서명 및 payload 암호화 (JWE,JWS)

2023. 7. 31. 00:00ASPNET/ASPNET 7

반응형

aspnet web api 의 기본적인 server 를 만들자.

아래를 참조해도 된다.

https://yogingang.tistory.com/404

 

Rest API Template 만들기 - EP 01 (Create , Swagger, Logging)

Create visual studio 2022 를 실행하고 Create a new project 를 선택한다. asp.net core web api 선택 Project name 정하기 Additional Information 설정 Create 하면 Project 가 생성된다. F5 를 누르면 실행 된다. 위와 같이 swagger

yogingang.tistory.com

 

JWT 을 이용한 인증 및 권한관리를 하는 경우 JWT 자체에 보안에 위험성이 몇가지 있다. 

 

  1. expire 시간이 지나기 전까지 token 을 무효화 하기 힘들다.
  2. payload 자체는 암호화 되지 않기 때문에 보안이 필요한 data 를 전달하기는 힘들다.
  3. jwt 자체는 단순 base64 encoding 된 data 이며 얼마든지 위변조 가능하다

위한 같은 문제들 때문에 jwt 를 이용한 인증 및 권한 관리는 많은 보안문제를 일으킨다. 

하지만 다른 대안을 찾기도 쉽지 않다. jwt 만큼 microservice 에 맞는 인증 방식을 찾기도 힘들다. 

 

1 번을 완벽히 해결하는 방법은 사실 없다고 봐야 한다. 

왜냐하면 token 의 무효화 여부를 알기 위해 server 상에 특정 storage에 jwt 정보를 저장하는 순간

해당 server 로의 부하가 집중될 것이고 이것은 microservice 의 병목 지점이 될것이다. 

 

그외에 문제들은 JWE(JSON Web Encryption), JWS (JSON Web Signature) 를 활용하여 해결한다.

 

이번 시간에는 aspnet core 에서 JWT 토큰을 생성할때 위 두가지를 이용하는 방법을 알아볼 것이다. 

 

우선 jwt 관련 여러가지 helper method 들이 존재하는 다음 package 를 설치하자.

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

그리고 payload encryption, decryption 을 위한 비대칭키 알고리즘으로  RSA 를 이용하자

그리고 서명과 서명확인을 위해 curved 비대칭키 알고리즘인 ECDsa 를 이용하자

세부적인 bit 라든지 algorithm 은 직접 customizing 해도 된다. 

한번 생성하여 재상용할 것이는 static class Helper 를 사용하여 활용하자

public static class JweTokenConfigHelper
{
    private static RSA _encryptionKey = RSA.Create(3072); // public key for encryption, private key for decryption
    private static ECDsa? _signingKey = ECDsa.Create(ECCurve.NamedCurves.nistP256); // private key for signing, public key for validation

    private static string _encryptionKid = "8524e3e6674e494f85c5c775dcd602c5";
    private static string _signingKid = "29b4adf8bcc941dc8ce40a6d0227b6d3";

    public static RsaSecurityKey PrivateEncryptionKey = new RsaSecurityKey(_encryptionKey) { KeyId = _encryptionKid };
    public static RsaSecurityKey PublicEncryptionKey = new RsaSecurityKey(_encryptionKey.ExportParameters(false)) { KeyId = _encryptionKid };
    public static ECDsaSecurityKey PrivateSigningKey = new ECDsaSecurityKey(_signingKey) { KeyId = _signingKid };
    public static ECDsaSecurityKey PublicSigningKey = new ECDsaSecurityKey(ECDsa.Create(_signingKey.ExportParameters(false))) { KeyId = _signingKid };

}

 

이제 이 정보들을 이용하여 JWT 를 생성해 보자

private string GenerateJweToken(string id)
{
    var privateSigningKey = JweTokenConfigHelper.PrivateSigningKey;
    var publicEncryptionKey = JweTokenConfigHelper.PublicEncryptionKey;

    // JwtSecurityTokenHandler 의 향상된 버전 JsonWebTokenHandler
    var handler = new JsonWebTokenHandler();

    string token = handler.CreateToken(new SecurityTokenDescriptor
    {
        Audience = "IdentityServer",
        Issuer = "https://localhost:7172", 
        Claims = new Dictionary<string, object> { { "sub", id } },

        // private key for signing
        SigningCredentials = new SigningCredentials(
            privateSigningKey, 
            SecurityAlgorithms.EcdsaSha256),

        // public key for encryption
        EncryptingCredentials = new EncryptingCredentials(
            publicEncryptionKey, 
            SecurityAlgorithms.RsaOAEP, 
            SecurityAlgorithms.Aes256CbcHmacSha512),
        Expires = DateTime.UtcNow.AddMinutes(10)
    }); ;

    return token;
}

자 이제 되었다. 

 

이것을 이용하여 JWT 를 생성하고 그 값을 복사한 후 https://jwt.io/ 에 가서  붙혀넣기 해보자

잘못된 base64 encoding 이라고 나오고 오른쪽 에도 header 를 제외한 다른 내용은 보지 못할것이다. 

 

이제 이 JWT 를 validation 하는 custom authorization handler 를 생성해보자

private bool ValidateJweToken(string token)
{
    try
    {
        var handler = new JsonWebTokenHandler();
        TokenValidationResult result = handler.ValidateToken
                            (token,
                            new TokenValidationParameters
                            {
                                ValidAudience = "IdentityServer",
                                ValidIssuer = "https://localhost:7172",

                                // public key for signing
                                IssuerSigningKey = JweTokenConfigHelper.PublicSigningKey,

                                // private key for encryption
                                TokenDecryptionKey = JweTokenConfigHelper.PrivateEncryptionKey
                            });

        return result.IsValid;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "ValidateJweToken error");
    }

    return false;
}

IssuerSigningKey 에 PublicSigningKey 를 준다. (privatekey 로 signing 했으니 publickey 로 validation 해야 한다.)

TokenDecryptionKey 에 PrivateEncryptionKey 를 준다. ( publickey 로 encryption 했으니 privatekey 로 decryption 해야한다.)

 

위와 같이 하고 CustomAuthorizationHandler 에 위 코드를 이용하여 validation check 하도록 한다. 

그러면 정상 처리 되는 것을 확인 할 수 있을 것이다. 

 

 

 

관련영상

https://youtu.be/-jbzcM5W6WI

반응형