ASP.NET Core 8.0에서 Basic Authentication 구현하기

  • 16 minutes to read

이 강좌에서는 ASP.NET Core 8.0에서 Basic Authentication을 구현하는 방법에 대해 설명합니다. Basic Authentication은 웹 API에 대한 간단한 보안을 제공하는 인증 방식입니다. 사용자 이름과 비밀번호를 Base64로 인코딩된 문자열로 전송하여 인증을 수행합니다. 이 방법은 간단하게 구현할 수 있지만, 중요한 프로젝트에서는 더 강력한 인증 방식을 사용하는 것이 좋습니다.

Basic Authentication 간단한 소개

Basic Authentication은 웹에서 가장 간단한 형태의 인증 방식 중 하나입니다. 이 방식은 클라이언트가 사용자 이름과 비밀번호를 결합하여 인코딩된 문자열 형태로 서버에 전송하는 방식을 사용합니다. 주로 HTTP 프로토콜에서 사용되며, 요청 헤더의 Authorization 필드에 인증 정보를 포함시켜 전송합니다.

Basic Authentication 동작 과정

  1. 클라이언트가 사용자 이름과 비밀번호를 취합하여 "username:password" 형식의 문자열을 생성합니다.
  2. 생성된 문자열을 Base64로 인코딩합니다. 인코딩된 문자열은 원본 문자열을 직접 전송하는 것보다 약간의 보안성을 제공하지만, 여전히 쉽게 디코딩할 수 있으므로 안전하게 간주되지는 않습니다.
  3. 인코딩된 문자열을 HTTP 요청 헤더의 Authorization 필드에 "Basic "을 앞에 붙여 전송합니다. 예를 들어, 인코딩된 문자열이 "QWxhZGRpbjpPcGVuU2VzYW1l"인 경우 헤더는 다음과 같습니다.
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
  1. 서버는 인증 헤더를 받아 Base64로 인코딩된 문자열을 디코딩하고, 사용자 이름과 비밀번호를 추출합니다. 이후 서버는 해당 사용자 이름과 비밀번호가 유효한지 확인하고, 인증 결과에 따라 적절한 응답을 전송합니다.

Basic Authentication의 단점

  1. 보안성이 낮습니다: Base64 인코딩은 암호화 방식이 아니기 때문에, 사용자 이름과 비밀번호는 네트워크 상에서 쉽게 노출될 수 있습니다. 따라서 HTTPS 같은 보안 프로토콜을 함께 사용하는 것이 필수적입니다.
  2. 인증 정보가 각 요청마다 전송되므로, 서버 부하가 증가할 수 있습니다.
  3. 기능이 제한적입니다: 기본 인증은 권한 관리, 로그아웃 기능 등 고급 기능을 제공하지 않습니다. 이러한 기능이 필요한 경우, 보다 발전된 인증 방식인 OAuth, JWT 등을 사용하는 것이 좋습니다.

ASP.NET Core에서 Basic Authentication 구현하기

ASP.NET Core에서 Basic Authentication을 구현하는 방법에 대해 설명합니다. 다음 코드를 사용하여 인증을 설정합니다.

builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

BasicAuthenticationHandler 클래스는 새로운 파일에 클래스를 생성해야 합니다.

코드 설명

  1. builder.Services.AddAuthentication("BasicAuthentication"): 이 메서드는 인증 서비스를 구성하고, 기본 인증 스키마를 "BasicAuthentication"으로 설정합니다.
  2. AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null): 이 메서드는 "BasicAuthentication"이라는 이름의 인증 스키마를 추가하고, BasicAuthenticationHandler 클래스를 이 스키마의 핸들러로 지정합니다. 여기서 AuthenticationSchemeOptions는 인증 스키마에 대한 옵션을 나타내며, BasicAuthenticationHandler는 Basic Authentication 과정을 처리하는 로직을 구현하는 클래스입니다.

BasicAuthenticationHandler 구현

이렇게 설정한 후, BasicAuthenticationHandler 클래스를 정의하여 인증 과정을 처리할 수 있습니다. 해당 클래스에서는 인증 헤더를 검사하고, 사용자 이름과 비밀번호를 추출한 뒤, 이를 사용하여 사용자를 인증하는 로직을 구현해야 합니다. 인증이 완료되면, 인증된 사용자에 대한 클레임을 생성하고, 이를 사용하여 인증된 사용자를 나타내는 ClaimsPrincipal 객체를 생성합니다.

주의사항

위의 코드를 사용하여 ASP.NET Core 프로젝트에서 Basic Authentication을 구현하면, 해당 프로젝트에서 요청 시 사용자 이름과 비밀번호를 통해 인증을 수행할 수 있습니다. 그러나 앞서 언급했듯이, Basic Authentication은 보안상의 한계로 인해 HTTPS를 함께 사용하는 것이 좋으며, 더 나은 인증 방식을 사용할 수 있는 경우에는 OAuth, JWT 등의 보다 발전된 인증 방식을 사용하는 것이 좋습니다.

시작하기 전에

  1. Visual Studio에서 새로운 ASP.NET Core Web API 프로젝트를 생성합니다.
  2. 프로젝트에서 적절한 데이터베이스 연결과 사용자 인증을 설정합니다. 이 예제에서는 데이터베이스 연결과 사용자 인증에 대한 구현을 생략합니다.

Basic Authentication 미들웨어 생성 맛보기

  1. Middleware 폴더를 생성하고, 폴더 내에 BasicAuthenticationMiddleware.cs 파일을 생성합니다.

  2. BasicAuthenticationMiddleware.cs 파일에서 다음 코드를 추가하여 미들웨어를 구현합니다.

using System;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

public class BasicAuthenticationMiddleware
{
    private readonly RequestDelegate _next;

    public BasicAuthenticationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.ContainsKey("Authorization"))
        {
            context.Response.StatusCode = 401;
            context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
            return;
        }

        var authHeaderValue = AuthenticationHeaderValue.Parse(context.Request.Headers["Authorization"]);

        if (authHeaderValue.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase))
        {
            var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderValue.Parameter)).Split(':');
            var username = credentials[0];
            var password = credentials[1];

            // Validate user credentials here (e.g., check the username and password against a database)

            if (IsValidUser(username, password))
            {
                await _next(context);
                return;
            }
        }

        context.Response.StatusCode = 401;
        context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
    }

    private bool IsValidUser(string username, string password)
    {
        // 실제, 아이디와 암호에 대한 검증 코드가 들어오는 곳
        return true;
    }
}
  1. Startup.cs 파일에서 Configure 메서드에 다음 코드를 추가하여 미들웨어를 등록합니다.
app.UseMiddleware<BasicAuthenticationMiddleware>();

이제 Basic Authentication이 구현되었습니다. API에 요청을 보낼 때 Authorization 헤더에 Basic 키워드와 Base64로 인코딩된 사용자 이름과 비밀번호를 포함시켜야 합니다.

예를 들어, 사용자 이름이 user이고 비밀번호가 password인 경우, Authorization 헤더는 다음과 같이 설정됩니다.

Authorization: Basic dXNlcjpwYXNzd29yZA==

여기서 dXNlcjpwYXNzd29yZA==는 Base64로 인코딩된 "user:password" 문자열입니다.

테스트하기

  1. 프로젝트를 빌드하고 실행합니다.
  2. Postman과 같은 API 테스트 도구를 사용하여 인증이 필요한 API 엔드포인트에 요청을 보냅니다. 요청 헤더에 앞서 설명한 대로 Authorization 헤더를 추가합니다.
  3. 올바른 사용자 이름과 비밀번호를 사용하여 요청을 보내면 API가 정상적으로 응답합니다. 잘못된 사용자 이름과 비밀번호를 사용하거나 Authorization 헤더를 생략하면 401 상태 코드를 반환합니다.

이 강좌에서는 ASP.NET Core 8.0에서 Basic Authentication을 구현하는 방법을 살펴보았습니다. 이 방식은 간단한 인증을 필요로 하는 경우에 유용할 수 있지만, 실제 프로덕션 환경에서는 더 안전한 인증 방식을 사용하는 것이 좋습니다. OAuth 2.0, OpenID Connect 등의 인증 프로토콜을 사용하여 API를 보호하는 것을 고려해 보세요.

ASP.NET Core에서 커스텀 핸들러를 사용한 기본 인증 구현하기

이 글에서는 ASP.NET Core 애플리케이션에서 커스텀 핸들러를 사용하여 기본 인증(Basic Authentication)을 구현하는 방법을 설명합니다.

1. 사용자 서비스 및 인터페이스 구현

먼저, 사용자 정보를 검색하고 인증을 수행하는 IUserService 인터페이스와 User 클래스, 그리고 UserService 클래스를 정의합니다.

public interface IUserService
{
    Task<User> Authenticate(string username, string password);
}

public class User
{
    public string Id { get; internal set; }
    public string Username { get; internal set; }
}

public class UserService : IUserService
{
    public Task<User> Authenticate(string username, string password)
    {
        if (username != "administrator" || password != "Pa$$w0rd")
        {
            return Task.FromResult<User>(null);
        }

        var user = new User
        {
            Username = username,
            Id = Guid.NewGuid().ToString("N")
        };

        return Task.FromResult(user);
    }
}

2. BasicAuthenticationHandler 클래스 구현

기본 인증을 처리하는 BasicAuthenticationHandler 클래스를 구현합니다.

using System;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace VisualAcademy.Authentication
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;
        private readonly TimeProvider _timeProvider;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            TimeProvider timeProvider, // TimeProvider 주입
            IUserService userService)
            : base(options, logger, encoder, clock: null)
        {
            _userService = userService;
            _timeProvider = timeProvider; // 주입된 TimeProvider 저장
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            User user;

            try
            {
                // Authorization 헤더 파싱
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter ?? string.Empty);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];

                // 사용자 인증
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Error Occurred. Authorization failed.");
            }

            if (user == null)
            {
                return AuthenticateResult.Fail("Invalid Credentials");
            }

            // 사용자 인증 성공 시 클레임 생성
            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id),
                new Claim(ClaimTypes.Name, user.Username),
                // TimeProvider를 활용한 현재 시간 추가 (예시)
                new Claim("AuthenticationTime", _timeProvider.GetUtcNow().ToString("o")) // ISO 8601 포맷
            };

            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }
}
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace VisualAcademy.ApiService.Security
{
    public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private const string FixedEmail = "admin@visualacademy.com";
        private const string FixedPassword = "securepassword";

        public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
        {
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
            {
                return Task.FromResult(AuthenticateResult.Fail("Authorization header missing."));
            }

            try
            {
                var authHeader = Request.Headers["Authorization"].ToString();
                if (!authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
                {
                    return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header."));
                }

                var encodedCredentials = authHeader.Substring("Basic ".Length).Trim();
                var decodedBytes = Convert.FromBase64String(encodedCredentials);
                var decodedCredentials = Encoding.UTF8.GetString(decodedBytes);
                var credentials = decodedCredentials.Split(':');

                if (credentials.Length != 2)
                {
                    return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header format."));
                }

                var email = credentials[0];
                var password = credentials[1];

                if (email != FixedEmail || password != FixedPassword)
                {
                    return Task.FromResult(AuthenticateResult.Fail("Invalid email or password."));
                }

                var claims = new[] { new Claim(ClaimTypes.Name, email) };
                var identity = new ClaimsIdentity(claims, Scheme.Name);
                var principal = new ClaimsPrincipal(identity);
                var ticket = new AuthenticationTicket(principal, Scheme.Name);

                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
            catch
            {
                return Task.FromResult(AuthenticateResult.Fail("Error occurred during authentication."));
            }
        }
    }
}

TimeProvider 주입

TimeProviderProgram.cs에서 DI(Dependency Injection)로 등록합니다.

Program.cs 변경
var builder = WebApplication.CreateBuilder(args);

// TimeProvider를 서비스로 추가
builder.Services.AddSingleton(TimeProvider.System);

builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

3. Program.cs 파일에 인증 구성 추가

Program.cs 파일에서 인증 구성을 추가합니다. builder.Services.AddAuthentication("BasicAuthentication")와 AddScheme 메서드를 사용하여 BasicAuthenticationHandler를 등록합니다.

builder.Services.AddAuthentication("BasicAuthentication")
    .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>
    ("BasicAuthentication", null);

또한, UserService를 의존성 주입 컨테이너에 등록합니다.

builder.Services.AddScoped<IUserService, UserService>();

마지막으로, app.UseAuthentication() 및 app.UseAuthorization()를 요청 파이프라인에 추가합니다.

app.UseAuthentication();
app.UseAuthorization();

이제 ASP.NET Core 애플리케이션에서 커스텀 핸들러를 사용하여 기본 인증을 구현하였습니다. 클라이언트가 요청 시 인증 헤더에 사용자 이름과 비밀번호를 전달하면, 서버는 이를 처리하여 인증을 수행합니다. 하지만 기본 인증은 상대적으로 보안이 약하기 때문에, HTTPS와 함께 사용하는 것이 좋습니다. 가능하다면 더 안전한 인증 방식(예: OAuth, JWT 등)을 사용하는 것이 좋습니다.

Web API 테스트

@DotNetNoteApi_HostAddress = http://localhost:5273

GET {{DotNetNoteApi_HostAddress}}/weatherforecast/
Accept: application/json

###

GET https://localhost:7215/WeatherForecast
Accept: application/json
Authorization: Basic QWRtaW5pc3RyYXRvcjpQYSQkdzByZA==

###

Swagger UI에서 Basic Authentication 구성하기

Swagger UI에 Basic Authentication을 위한 Authorize 버튼을 추가하기 위해서는 다음 단계를 따르세요.

1. Swashbuckle.AspNetCore 설치

먼저, 프로젝트에 Swashbuckle.AspNetCore 라이브러리를 설치합니다. 이 라이브러리는 ASP.NET Core 프로젝트에 Swagger UI와 Swagger 문서 생성기를 쉽게 통합할 수 있게 해줍니다. NuGet 패키지 관리자를 사용하여 설치할 수 있습니다.

Install-Package Swashbuckle.AspNetCore

2. Swagger 문서 생성기 설정

Startup.cs 파일 또는 Program.cs 파일에서 Swagger 문서 생성기를 설정합니다. AddSwaggerGen 메서드를 사용하여 Swagger 문서 생성기를 구성하고, Basic Authentication을 위한 보안 정의를 추가합니다.

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

    // Basic Authentication을 위한 보안 정의 추가
    c.AddSecurityDefinition("basic", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "basic",
        In = ParameterLocation.Header,
        Description = "Basic Authentication을 사용하여 인증합니다. 'Username:Password'를 Base64로 인코딩하여 전송하세요."
    });

    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "basic"
                }
            },
            new string[] {}
        }
    });
});

3. Swagger UI와 문서 생성기 미들웨어 추가

Configure 메서드 또는 app 구성 부분에서 Swagger UI와 Swagger 문서 생성기 미들웨어를 추가합니다. 이를 통해 애플리케이션 실행 시 Swagger UI를 통해 API 문서에 접근할 수 있습니다.

app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    // Swagger UI에서 Basic Authentication을 사용할 수 있도록 Authorize 버튼 추가
    c.DefaultModelsExpandDepth(-1); // 모델 목록을 기본적으로 접히게 설정
});

4. 프로젝트 실행 및 테스트

위의 설정을 완료한 후 프로젝트를 실행하면, Swagger UI에서 Authorize 버튼이 표시됩니다. 이 버튼을 클릭하면 사용자 이름과 비밀번호를 입력하는 대화 상자가 나타납니다. 올바른 사용자 이름과 비밀번호를 입력하고 Authorize 버튼을 클릭하면, 입력한 인증 정보를 사용하여 API 요청이 수행됩니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com