.NET6 C# 으로 구현한 기본 인증 Basic Authentication
이전 글에서 Basic Authentication이 어떻게 구현 되는지 설명은 했는데, 사실 코드가 어떻게 구동 되는지 보지 않으면 이해가 되지 않을 수도 있을 것 같아서 이렇게 github gist 도 써보고자 한 번 코드로 기본 인증을 구현해보자
그리고 2021. 11월에 새로 나온 .NET6 의 새 기능 Minimal API 을 써보도록 하자
Minimal API 소회
이전의 .NET5, .NET Core 2~3.1 등등 많이 사용하면서 지내왔는데,
Minimal API 이거 진짜 nodejs로 개발 하는 느낌이다
(빠르게 몇 줄 만들면 express 촥~ 기능 촥~ 만들어지는 그런 느낌이랄까?
nodejs로 개발해보신 분들이라면 이해하는 감정일꺼임)
Minimal API vs Use Controller 사이에 다른 점을 확실히 이해하고 진행해보도록 한다
- BasicAuthenticationHandler.cs - 기본 인증 핸들러
- BasicAuthenticationOptions.cs - 기본 인증 옵션
- IdentityBasicAuthenticationHandler.cs - 비지니스만 추상화 시킨 인증 핸들러
- Program.cs - .NET6 Minimal API 의 실행 파일 (모든 구현은 여기서)
총 4개의 파일로 구성되어져 있는데, 사실 나머지는 내 의도일 뿐이고, Program.cs 하나로 다 몰아버리면 모든게 되는게 신기한 Minimal API (짝짝짝)
총 24 lines 의 코드로 기본 인증이 가능한 서버를 만들어낸다 (물론 진짜 구현된 class 파일은 따로 있는게 함정)
중요한 설명들은 아래 코드에서 모든게 다 설명이 되고 구현을 한다
몇 줄 되지 않는 Program.cs 로 간단하고 빠른 개발이 가능한 느낌이라 정말 Micro Service 가 많아지겠거니 하는 생각은 들지만... (실무자는 더이상의 말은 생략한다ㅋㅋㅋ)
다시 돌아가서 코드를 만들어보도록 하자
이 곳은 기본 인증을 구현하는게 목적이니, 그것에 포커스를 맞춰서 진행한다
기존에 .NET Core나 .NET5 에서 Startup.cs 에서 있던 코드를 그대로 따라왔다고 생각하자ISerivceCollection service
에 .AddAuthentication()
를 정의(Go to define)로 보게 되면 AuthenticationBuilder
를 return 하는 것을 볼 수 있다. 따라가자.
AuthenticationBuilder 빌더의 정의를 또 보게 되면 AddScheme<TOption, THandler>()
을 확인할 수 있다. AddScheme<TOptions, THandler> 는 무엇인지는 클래스에 이미 나와있다.
where TOptions : AuthenticationSchemeOptions, new()
where THandler : AuthenticationHandler<TOptions>
아하! AuthenticationSchemeOptions,
그리고 AuthenticationHandler<TOptions>
구나! 이게 무엇인지 자세히 모르겠으니 MSDN 을 활용한다.
All authentication schemes that use derived AuthenticationSchemeOptions and the associated AuthenticationHandler:
모든 인증 스킴들은 that 이하다 (갑분 영어 독해??)
- AuthenticationSchemeOptions으로부터 파생되고,
- AuthenticationHandler 와 관련이 있다
결론은 저녀석들을 사용해서 만들어야 한다는 소리다
뭔가 석연치가 않다. 단지 기본 인증 Basic Authentication 하나 만들려고 하는데 이미 방법도 다 알고 있는데 - base64-encoded(A:B) - 왜 이렇게 어려울 것인가? 를 생각하면서 이 곳에서는 저 듣보들을 사용한다고 하는데 도대체 내가 왜 이런걸 알아야 하는거지? 하는 생각이 들 것이다. 그저 이런 원리를 통해 만들어진다 라고 설명하는 중이다. 이 글을 보는 C# .NET 개발자라면, [Authorize] 필터가 제 역할을 하기 위해서 밑밥을 까는게 현재 AuthenticationHanlder 의 구현이다. (라고 생각하면 이해하기가 좀 더 쉬울려나??)
다시 돌아가서 BasicAuthenticationHandler.cs 파일을 살펴보자
추상(abstract) 클래스 BasicAuthenticationHandler
는 AuthenticationHandler<BasicAuthenticationOptions>
를 상속(inherit) 한다
어??? 아까 걔다. AuthenticationHandler<TOptions> 잠깐! 그럼 TOptions 에 있는 BasicAuthenticationOptions
는 뭐지?
BasicAuthenticationOptions.cs 파일을 보면 BasicAuthenticationOptions
는 AuthenticationSchemeOptions
를 상속 받고 있다
옴마! 다 쓰고 있네?? 위에 영어(해석 본)를 다시 보면
- AuthenticationSchemeOptions으로부터 파생되고, => 얘 꼭 써야해!
- AuthenticationHandler 와 관련이 있다 => 관련이면 일단 참고해둬!
에 딱인 상황이다
AuthenticationSchemeOptions 부터 살펴보자
public class BasicAuthenticationOptions : AuthenticationSchemeOptions
{
public string Realm => "Basic Auth Server";
}
BasicAuthenticationOptions 클래스를 만들고 Realm 프로퍼티를 만들어서 기본 값을 넣어준 상태다
Realm
: authentication parameter is reserved for use by authentication schemes that wish to indicate a scope of protection.
[참고 설명 : Realm (stackoverflow)]
사실 이 부분은 굳이 BasicAuthenticationOptions
를 사용하지 않고, AuthenticationSchemeOptions
만 사용해도 무방하다.
예를 들면 아래와 같이
services
.AddAuthentication("BasicAuthenticaion")
.AddScheme<AuthenticationSchemeOptions, IdentityBasicAuthenticationHandler>("BasicAuthenticaion", null);
public class ThisIsExampleClassName : AuthenticationHandler<AuthenticationSchemeOptions>
{
..
}
이런 형태로 AuthenticationSchemeOptions만 사용해도 가능하다
단지 개념적으로 Realm 을 추가 했던 과거의 나놈이 있어서 코드에는 들어간 내용일 뿐이다
(전혀 개의치 않고 AuthenticationSchemeOptions만 사용해도 된다)
우리가 상속해야 할 AuthenticationHandler
클래스를 살펴보자.
public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
{
.... 뭐 이것저것 써 있음
....
....
protected abstract Task<AuthenticateResult> HandleAuthenticateAsync();
....
....뭐 이것저것 써 있음
....
}
보는 바와 같이 추상(abstract) 클래스이다 - 추상 클래스면 필수로 구현 해야 하는 키워드 abstract
를 찾아야 한다. 클래스 중간에 Task<AuthenticateResult> HandleAuthenticateAsync();
를 필수로 구현해야 하게 되어있다
자 이제 드디어 만든 코드 설명을 할 수 있게 되었다 (감격)
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
class BasicAuthenticationHandler 는 AuthenticationHandler<TOptions> 를 상속 받고, 그에 따라 필수로 HandlerAuthentcateAsync()
메소드를 구현해야만 했다
어렵게 생각하지 말고 우선 새로운 클래스 하나를 만들고, AuthenticationHandler<TOptions> 를 상속해보자
갓갓(God) Visual Studio 님께서는 바로 빨간줄을 그려주시고, 해당 구현을 자동으로 해주신다
감사한 마음으로 구현을 누르고, 또 한 번 감사하는 마음으로 생성자를 생성한다
단박에 후다닥 코드가 만들어진다
사실 여기 까지 만들면 다 한거나 다름 없다.
이후 부터는 진짜 구현이다. Basic Authentication 의 Spec 을 구현하면 된다.
인증 미들웨어가 실행 되었을 때 DefaultAuthenticateScheme
을 호출하면 HandleAuthenticateAsync() 메소드가 실행된다.
결과가 리턴 되는 값으로 AuthenticateResult 가 보이는데, 이 클래스의 정의로 들어가면 아래와 같이 3가지의 조건이 나온다
AuthenticateResult.NoResult()
아무 결과 없음을 생성 (null도 가능)AuthenticateResult.Fail(Exception or string)
실패를 나타내며, 예외(Exception) 또는 오류 메시지(failureMessage)를 제공가능AuthenticateResult.Success(ticket)
성공을 나타내며 ticket 은 사용자 정보(UserInfo)가 포함된AuthenticationTicket
위의 Github Gist 의 코드를 참고 하면서 보면 Request Header 의 Authorization : Basic <credentials>
로 들어오는 것을 검증하고, credentials 분리 하고, 해당 내용을 userid , password 로 나눠서 Biz Logic Process를 따로 만들 수 있도록 추상 메소드를 하나 더 만들었다.
사실 회사에서 그냥 단일로 쓰다가, 다른 곳을 봐야 하는 요구사항이 생겨서 Biz Logic 만 따로 구현하도록 만들었다
뭔가 더 설명할 내용이 있을 것 같지만, 이미 많은 말들을 남겨놨고, 이 부분에서 얻어갈 사람들은 알아서 얻을 수 있을 것이라 생각하며 이만