2026년 10월 22일 목요일

콘솔기반 asp.net 케스트렐 어플리케이션 생성하고 사설아이피 인증서 만들기

서론:
C#으로 작업을 하다보면 웹서비스를 만들어야 하는 상황이 온다.
이 상황에서 보통은 2의 루트로 나뉘에서 진행하게된다.
HttpListener클래스를 이용해서 여기에 메서드를 등록하고 서비스 하는방법
정식적으로 ASP.Net을 이용해서 케스트렐용 어플리케이션을 만들고 프록시로 
웹서버를 전면에두거나 IIS에서 배포하는 방법등이 거론된다.
문제는 첫번째 방법은 어떤 프로젝트에나 들어갈 수 있지만 콜백 등록이나 DI처리
나 Authorization처리 같은것의 처리가 난해하고 HttpRequest에 대한 디테일한
파이프라인 설정을 하지 못한다. 두번째 방법은 전부 다 갖춰져 있지만 인스턴스한 
느낌이들지 않아서 특정 프로젝트에 소속되기 힘들다.
예를들어 다른사람과 주고 받을 간단한 restapi서버를 만들어야 되고 url이 20개 
미만인 상황이고 이 프로그램이 윈도우의 서비스나 리눅스에 데몬으로 들어가야 
한다거나 아니면 여타 다른 윈폼이나 wpf의 프로그램에서 Sub프로세스로 
띄워져야 하는 경우가 있을 수 있다. 이런상황에서 https로 암호화 통신까지 해야하고  
게다가 도메인은 없고 192.168.1.10 과 같은 사설IP로 https 통신을 해야하는 
상황이 있을 수 있다. 이경우는 앞서 언급한 2가지 케이스 전부 애매한 상황에 놓여진다. 
게다가 이런 케이스가 동시에 한컴퓨터에서 다른 도메인에서 벌어져야 한다면?
이럴때는 하나의 솔류션에서 여러개의 콘솔기반 케스트렐로 작성한후 레고블럭 
쌓듯이 처리하는것이 이롭다.  그래서 이번에는 Mini RestAPI서버를 케스트렐로
만들고 이후 각각의 어플리케이션에 사설인증서를 달아야한다.
그 방법을 알아보자.



[ 콘솔기반 mini restapi 케스트렐 어플리케이션 ]

먼저 솔루션을 하나 만들고 빈 웹어플리케이션을 하나만든다.
간단한 REST API를 만들것이므로 GET방식과 POST방식만 지원하는
인터페이스를 신설(IAPIAction)하고 각요청에 대응하는 인터페이스를 구현한 
클래스를 만든다. 이렇게 만든 클래스들을 스케닝할수 있는 클래스를 만들고
builder.Service에 리플렉션을 이용하여 모두 등록처리한다.
나중에 헤더등에서 전달되어 오는 Authrization 같은것을 처리하기 위해
HttpContext는 주입을 받는 방향으로 설계한다.
기본적으로 WebApplication은  내부적으로 DI를 받을수 있는 Services를
내장하고 있다. 따라서 생성자에서 로거나 어플리케이션DB를 받을수 있다.
이건 나중에 알아서 하면되고 초기 스케폴딩을 먼저 보자.


/// <summary>
/// 액션 인터페이스
/// </summary>
public interface IAPIAction
{
    string HttpMethod { get; }
    string TargetUrl { get; }
    IResult DoAction(HttpContext context);
}

/// <summary>
/// 헬스체크용 API URL 구현체..  이런것을 여러개 만들면 된다.
/// </summary>
public class HealthAction: IAPIAction
{
    public string HttpMethod => "GET";
    public string TargetUrl => "/api/heath";
    public IResult DoAction(HttpContext context)
    {
        return Results.Ok(new
        { 
            Status = "OK",
            Time = DateTimeOffset.UtcNow
        });
    }
}

/// <summary>
/// API Action 검색
/// </summary>
public static class APIActionScanner
{
    public static List<Type> GetActionTypes()
    {
        return Assembly.GetExecutingAssembly()
            .GetTypes()
            .Where(type =>
                type.IsClass &&
                !type.IsAbstract &&
                typeof(IAPIAction).IsAssignableFrom(type))
            .ToList();
    }
}

/// <summary>
/// 프로그램
/// </summary>
public class Program
{
    public static async Task Main(string[] args)
    {
        //초반 셋업파일
        bool bLoadedConfig = LoadConfigFile();
        bool bLoadedDatabase = DataBaseInit();
        
        //웹어플리케이션 빌더
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        
        //케스트렐 연결옵션
        builder.WebHost.ConfigureKestrel(serverOptions =>
        {
            //http 옵션
            serverOptions.ListenAnyIP(MyServerPort);
        });
        
        //API Action들 리스트
        List<Type> actionTypes = APIActionScanner.GetActionTypes();
        foreach (Type actionType in actionTypes) { builder.Services.AddSingleton(actionType); }
        
        //웹어플리케이션 빌드
        WebApplication app = builder.Build();
        
        //API Action 인스턴스 생성 및 URL 맵핑
        foreach (Type actionType in actionTypes)
        {
            IAPIAction action = (IAPIAction)app.Services.GetRequiredService(actionType);
            switch (action.HttpMethod.ToUpperInvariant())
            {
                case "GET":
                    app.MapGet(action.TargetUrl, (HttpContext context) => action.DoAction(context));
                    break;
                case "POST":
                    app.MapPost(action.TargetUrl, (HttpContext context) => action.DoAction(context));
                    break;
                default:
                    throw new InvalidOperationException($"지원하지 않는 HTTP 메서드: {action.HttpMethod}");
            }
        }
        
        //실행
        await app.RunAsync();
    }
    
    
    /// <summary>
    /// 설정파일로드
    /// </summary>
    public static bool LoadConfigFile() => true;
    
    
    /// <summary>
    /// 데이터베이스 로드
    /// </summary>
    public static bool DataBaseInit() => true;

}




[ 케스트렐 프로그램에 인증서 달기 ]

- 먼저 openssl이 설치되어야 한다 openssql을 설치하고 bin폴더를 path로
잡은후 명령창에서 openssql -version 이라고 타입하여 내용이 나오는지 확인

- 사전 개념 사항은 CSR파일과 CRT 파일이 있는데
CSR파일은 무서명된 인증서 신청파일 이고 이것은 최종 인증서가 아님
CRT파일은 서명이 끝난 최종 인증서 파일임  CA(루트인증서와 개인키)를 CSR을 보고
검토한뒤 CA의 도장을 찍어서 발행한 완성본 파일임

- 인증서를 관리할 폴더를 하나만들고 그 경로에서 다음 명령을 타이핑한다.


1. CA 개인키 생성  (나자신이 인증기관이므로 인증기관용 CA 개인키 생성)
openssl genrsa -out rootCA.key 4096

2. CA 루트 인증서 생성 (유효기간 10년)  1번에서 만든 키로 CA 인증서를 발급한다.
rootCA.crt 파일을 만들어야함
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/CN=MyGoodUnitRootCA"

3. 서버용 인증서 설정파일 미리 준비
폴더에 server.ext 파일을 하나만들고 내용을 다음과 같이 기록한 후 저장한다.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
# 서비스할 실제 사설 IP 주소로 변경
IP.1 = 192.168.1.100
IP.2 = 127.0.0.1


4. 서버용 개인키 생성  
openssl genrsa -out server.key 2048


5. 서버용 CSR파일 생성 (서버키로 CA에 서명요청을할 파일) (아이피변경)
openssl req -new -key server.key -out server.csr -subj "/CN=192.168.1.100"


6. 서버용 CRT파일 생성 (CA기관이 요청서인 server.csr보고 개인키로 서명한 서버인증서)
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile server.ext


7. 이렇게 만들어진 server.key파일과 server.crt 파일을 하나의 server.pfx 파일로 비밀번호와 함께 합쳐서 하나로 만들기 (myverygoodpassword 는 자신만의 비밀번호로 변경)
openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt -passout pass:myverygoodpassword



8. 이제 최종 server.pfx 파일이 만들어졌으니 케스트렐 서버에서 다음 부분만 변경하면됨
(변경전)
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    //http 옵션
    serverOptions.ListenAnyIP(MyServerPort);
});

(변경후)
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    //https 옵션
    serverOptions.ListenAnyIP(MyServerPort, listenOptions =>
    {
        listenOptions.UseHttps("server.pfx", "myverygoodpassword");
    });
    //http 옵션 +1 로 처리 (https만 필요한경우 주석으로 막기)
    serverOptions.ListenAnyIP((MyServerPort+1));
});



추가 참고 사항:  인증기관 CA의 인증서파일인 rootCA.crt 파일은 각 운영체제가 공증해야함
따라서 이 파일을 클라이언트에 넘겨주고 해당 컴퓨터에서 더블클릭(윈도우)하면
이 기관을 신뢰할것인지 등록할 수 있음 (물론 이것 안해도 인증기관 무시하고 암호화 통신할수 있음)






댓글 없음:

댓글 쓰기