legacy application 은 single query, string parameter 의 모든 값을 암호화한 다음 ASP.NET Core MVC endpoint에 HTTP 요청을 했습니다.
이 게시물 에서는 ASP.NET Core MVC에서 Action Filters 를 활용 하여 action methods 의 개발 경험을 지금까지 사용해 온 것과 동일한 방식으로 만드는 방법을 알아봅니다 .
레거시 시스템은 오늘날 우리가 가지고 있는 모든 프로토콜과 보안 패턴에 액세스할 수 없기 때문에 창의적인 문제 해결을 해야 했을 수 있습니다. 우리의 경우에는 TripleDES를 사용하여 암호화 된 query string parameter 가 있으며 수신자가 해당 암호화 값을 해독 할 것으로 예상합니다.
/?secret=1w58y%2BC60jon25f%2F4VvVHUOX%2FIxs%2FEVx
이 기술은 URL의 secret 이 log 에 남을 수 있기 때문일 수 있습니다. 이유가 어째든.. 우리는 해결해야 할 문제가 발생했습니다.
IndexPost 라는 endpoint 에는 number 그리고 name 이라는 2개의 파라미터를 받습니다. 이 매개변수는 action 의 context 에 있을 때 값을 가질 것으로 예상됩니다.
[Route(""), HttpPost]
public IActionResult IndexPost(int number, string name)
{
return View("Index",
new IndexModel
{
Number = number,
Name = name
});
}
이 문제에 대한 간단한 솔루션이 있으며, 여기에서 Action Filters 가 포함됩니다 .
Action Filters 는 들어오는 요청을 가로채고 실행(execution) 파이프라인을 강화 시키는 좋은 방법입니다. 우리의 경우 기존 query string parameter 인 secret 를 변환하고, 해당 값을 해독하고 action method 에 매개 변수를 설정 하려고 합니다 .
우리의 최종 사용법은 구현된 EncryptedParameters 속성 을 사용할 것이며 , 적용 가능한 메소드에 decorate 할 것입니다.
[Route(""), HttpPost]
[EncryptedParameters("secret")]
public IActionResult IndexPost(int number, string name)
{
return View("Index",
new IndexModel
{
Number = number,
Name = name
});
}
EncryptedParameters 의 구현을 살펴보자,
using System;
using System.Globalization;
using System.Linq;
using System.Web;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Secrets.Models;
namespace Secrets.Controllers
{
public class EncryptedParameters : ActionFilterAttribute
{
public string ParameterName { get; }
public EncryptedParameters(string parameterName = "secret")
{
ParameterName = parameterName;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var config = context.HttpContext.RequestServices.GetRequiredService<IOptions<CryptoEngine.Secrets>>();
var encrypted = context.HttpContext.Request.Query[ParameterName].FirstOrDefault();
// decrypt secret
var decrypted = CryptoEngine.Decrypt(encrypted, config.Value.Key);
var collection = HttpUtility.ParseQueryString(decrypted);
var actionParameters = context.ActionDescriptor.Parameters;
foreach (var parameter in actionParameters)
{
try
{
var value = collection[parameter.Name];
if (value == null)
continue;
// set the action arguments to the values
// from the encrypted parameter
context.ActionArguments[parameter.Name] =
ConvertToType(value, parameter.ParameterType);
}
catch (Exception e)
{
context.ModelState.TryAddModelException(parameter.Name, e);
}
}
}
private static object? ConvertToType(string value, Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type);
if (value.Length > 0)
{
if (type == typeof(DateTimeOffset) || underlyingType == typeof(DateTimeOffset))
{
return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture);
}
if (type == typeof(DateTime) || underlyingType == typeof(DateTime))
{
return DateTime.Parse(value, CultureInfo.InvariantCulture);
}
if (type == typeof(Guid) || underlyingType == typeof(Guid))
{
return new Guid(value);
}
if (type == typeof(Uri) || underlyingType == typeof(Uri))
{
if (Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uri))
{
return uri;
}
return null;
}
}
else
{
if (type == typeof(Guid))
{
return default(Guid);
}
if (underlyingType != null)
{
return null;
}
}
if (underlyingType is not null)
{
return Convert.ChangeType(value, underlyingType);
}
return Convert.ChangeType(value, type);
}
}
}
ActionExecutingContext 를 사용하여 대상 endpoint 의 parameters 에 액세스할 수 있습니다. 그런 다음 우리의 secret 을 해독합니다. 이를 ActionArguments 에 설정하여 알고자 하는 파리미터들의 값들을 셋팅해 줍니다.
public override void OnActionExecuting(ActionExecutingContext context)
{
var config = context.HttpContext.RequestServices.GetRequiredService<IOptions<CryptoEngine.Secrets>>();
var encrypted = context.HttpContext.Request.Query[ParameterName].FirstOrDefault();
// decrypt secret
var decrypted = CryptoEngine.Decrypt(encrypted, config.Value.Key);
var collection = HttpUtility.ParseQueryString(decrypted);
var actionParameters = context.ActionDescriptor.Parameters;
foreach (var parameter in actionParameters)
{
try
{
var value = collection[parameter.Name];
if (value == null)
continue;
// set the action arguments to the values
// from the encrypted parameter
context.ActionArguments[parameter.Name] =
ConvertToType(value, parameter.ParameterType);
}
catch (Exception e)
{
context.ModelState.TryAddModelException(parameter.Name, e);
}
}
}
type 에 대한 변환은 primitives 및 nullable 유형을 처리하지만 복잡한 models 은 처리하지 않습니다. 이 부분은 특정 요구 사항에 맞게 이 코드를 자유롭게 수정해서 사용하시면 됩니다.
CryptoEngine 코드에 관심이 있는 사람들을 위해 여기 있습니다. 하지만 저는 제 자신을 암호화(cryptography) 전문가라고 생각하지 않습니다.
using System;
using System.Security.Cryptography;
using System.Text;
namespace Secrets.Models
{
/// <summary>
/// modified from the following post
/// https://dotnetcodr.com/2015/10/23/encrypt-and-decrypt-plain-string-with-triple-des-in-c/
/// </summary>
public static class CryptoEngine
{
public class Secrets
{
public string Key { get; set; }
}
public static string Encrypt(string source, string key)
{
var byteHash = MD5.HashData(Encoding.UTF8.GetBytes(key));
var tripleDes = new TripleDESCryptoServiceProvider
{
Key = byteHash,
Mode = CipherMode.ECB
};
var byteBuff = Encoding.UTF8.GetBytes(source);
return Convert.ToBase64String(tripleDes.CreateEncryptor()
.TransformFinalBlock(byteBuff, 0, byteBuff.Length));
}
public static string Decrypt(string encodedText, string key)
{
var byteHash = MD5.HashData(Encoding.UTF8.GetBytes(key));
var tripleDes = new TripleDESCryptoServiceProvider
{
Key = byteHash,
Mode = CipherMode.ECB
};
var byteBuff = Convert.FromBase64String(encodedText);
return Encoding.UTF8.GetString(
tripleDes
.CreateDecryptor()
.TransformFinalBlock(byteBuff, 0, byteBuff.Length));
}
}
}
사용 중인 이 action filter 를 보여주기 위해 간단한 ASP.NET Core MVC 응용 프로그램을 만들었습니다. view 부터 시작하여 일부 값을 암호화하고 EncryptedParameters 속성을 사용한 다음, endpoint 에 이를 게시합니다 .
@model IndexModel
@inject Microsoft.Extensions.Options.IOptions<CryptoEngine.Secrets> config
@{
ViewData["Title"] = "Home Page";
var values = "name=Khalid&number=57";
var secret = CryptoEngine.Encrypt(values, config.Value.Key);
var url = Url.Action("IndexPost", new {secret});
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
<p>Click this link to transmit secret values via Querystring</p>
<form action="@url" method="POST">
<p>@url</p>
<button type="submit">Submit</button>
</form>
</div>
@if (Model != null)
{
<section style="margin-top: 1em">
<div class="text-center">
<h2>Secrets</h2>
<div>
<label asp-for="Name"></label>
@Model.Name
</div>
<div>
<label asp-for="Number"></label>
@Model.Number
</div>
</div>
</section>
}
form 을 submit 하면 page 에 secrets 이 표시됩니다.
당신은 그것을 가지고 있습니다! 이 기술을 사용하여 들어오는 쿼리 문자열을 다른 매개변수 집합으로 변환할 수 있습니다. 이 기술은 강력하며 모든 MVC 작업에 적용하거나 전역적으로 적용할 수 있습니다.
항상 그렇듯이이 코드를 시작점으로 사용하고 특정 요구 사항에 맞게 변경하십시오.
작업 샘플을 가지고 놀고 싶다면 내 GitHub 리포지토리에서 코드를 찾을 수 있습니다 .
ASP.NET CORE 6.0 의 Session 사용해 보기 (4) | 2021.11.17 |
---|---|
Entity Framework Core 와 ASP.NET Core 를 활용한 cursor paging 사용해 보기 (0) | 2021.10.14 |
ASP.NET Core 웹 API(.NET 5)에서 MediatR을 사용하여 CQRS를 구현하는 방법 (2) | 2021.06.07 |
asp.net core 3.1 을 통해 aws 의 Lambda 서비스를 배포하여 실행 (0) | 2021.05.28 |
ASP.NET Core에서 HTTP Request Cancel 을 위한 CancellationToken 사용법 (1) | 2021.03.30 |