GN2.Common.Library
3.0.0
dotnet add package GN2.Common.Library --version 3.0.0
NuGet\Install-Package GN2.Common.Library -Version 3.0.0
<PackageReference Include="GN2.Common.Library" Version="3.0.0" />
<PackageVersion Include="GN2.Common.Library" Version="3.0.0" />
<PackageReference Include="GN2.Common.Library" />
paket add GN2.Common.Library --version 3.0.0
#r "nuget: GN2.Common.Library, 3.0.0"
#:package GN2.Common.Library@3.0.0
#addin nuget:?package=GN2.Common.Library&version=3.0.0
#tool nuget:?package=GN2.Common.Library&version=3.0.0
GN2.Common.Library
GN2.Common.Library 는 GN2.Core 를 사용하는 프로젝트가 반복해서 구현하는 일들을 모아둔 범용 유틸리티 패키지입니다.
웹 프레임워크에 의존하지 않으므로 API 서버, 워커, 콘솔 앱에서 똑같이 사용할 수 있습니다.
패키지 정보
- Target Framework:
net10.0 - Version:
3.0.0 - NuGet:
GN2.Common.Library - 의존성:
GN2.Core,Microsoft.Extensions.DependencyInjection.Abstractions,Microsoft.Extensions.Logging.Abstractions,Microsoft.Extensions.Options
구성
Abstracts/ IActionBridge · IBridgeRequest · IBridgeHandler · IBridgeBehavior
IObjectLinker · MappingExpression
IEmailSender · EmailMessage
ISecretProtector · IPasswordHasher
Bridge/ ActionBridge, AddActionBridge
Mapping/ ObjectLinker, AddObjectLinker
Security/ AesGcmSecretProtector, Pbkdf2PasswordHasher, HashText, Base64UrlText
Mail/ SmtpEmailSender, AddSmtpEmailSender
Text/ HtmlText
ActionBridge — 요청 디스패처
요청 하나에 핸들러 하나를 대응시키고, 필요하면 파이프라인으로 감쌉니다.
using GN2.Common.Library.Abstracts;
using GN2.Common.Library.Bridge;
services.AddActionBridge(options =>
{
options.RegisterServicesFromAssemblyContaining<Program>();
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
public sealed record GetArticle(long Id) : IBridgeRequest<ReturnValues<ArticleModel>>;
public sealed class GetArticleHandler : IBridgeHandler<GetArticle, ReturnValues<ArticleModel>>
{
public async Task<ReturnValues<ArticleModel>> HandleAsync(GetArticle request, CancellationToken cancellationToken = default)
{
// ...
}
}
var result = await bridge.SendAsync(new GetArticle(10), cancellationToken);
파이프라인 동작은 등록한 순서대로 바깥에서 안쪽으로 실행됩니다.
public sealed class LoggingBehavior<TRequest, TResponse> : IBridgeBehavior<TRequest, TResponse>
where TRequest : IBridgeRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger) => _logger = logger;
public async Task<TResponse> HandleAsync(TRequest request, BridgeHandlerDelegate<TResponse> next, CancellationToken cancellationToken = default)
{
_logger.LogDebug("{Request} 처리 시작", typeof(TRequest).Name);
var response = await next(cancellationToken);
_logger.LogDebug("{Request} 처리 완료", typeof(TRequest).Name);
return response;
}
}
- 요청 타입 하나에 핸들러는 하나만 등록할 수 있습니다. 둘 이상 발견되면 등록 시점에 예외가 발생합니다.
AddActionBridge는 여러 번 호출해도 중복 등록되지 않습니다.- 핸들러 수명은 기본
Transient,IActionBridge는 기본Scoped이며 옵션으로 바꿀 수 있습니다.
ObjectLinker — 경량 객체 매퍼
이름이 같고 타입이 호환되는 속성은 규약으로 복사하고, 나머지는 명시적으로 지정합니다.
using GN2.Common.Library.Mapping;
services.AddObjectLinker(options =>
{
options.CreateMap<Article, ArticleModel>()
.ForMember(d => d.UserName, s => s.User.FullName)
.ForMember(d => d.LocalizedUrls, s => s.Translations.Where(t => t.IsConfirmed).ToList())
.Ignore(d => d.InternalMemo);
});
var model = linker.Map<Article, ArticleModel>(article);
var models = linker.MapMany<Article, ArticleModel>(articles);
- 매핑 계획은 타입 쌍마다 한 번만 계산되고 인스턴스 단위로 캐시됩니다.
- 규약 매핑은 같은 이름 + 대입 가능한 타입(값 타입
T→T?포함)일 때 적용됩니다. 숫자 암시 변환(int→long)은 매핑하지 않습니다. - 대상 속성이 없거나 쓰기 불가능하면
ForMember구성 시점에 예외가 발생합니다. - 대상 타입은 참조 타입이어야 하며 매개변수 없는 생성자가 필요합니다.
Security — 암호화 · 해시
문자열 보호 (AES-256-GCM)
using GN2.Common.Library.Abstracts;
using GN2.Common.Library.Security;
services.AddSecretProtector(options =>
{
options.Passphrase = configuration["Security:Passphrase"]; // 보안 저장소에서 주입
options.Salt = "gn2-article-v1"; // 애플리케이션 고정값
});
var token = protector.Protect("010-0000-0000");
if (protector.TryUnprotect(token, out var phone))
{
// ...
}
- 결과는 URL·쿼리스트링에 그대로 넣을 수 있는 Base64Url 문자열입니다.
- 메시지마다 임의 nonce 를 사용하므로 같은 평문도 매번 다른 값이 됩니다. 보호된 값을 조회 키로 쓸 수 없습니다. 검색이 필요하면
HashText.Sha256Hex로 만든 별도 컬럼을 사용하세요. - 인증 태그로 변조를 감지합니다. 손상되거나 위조된 값은
TryUnprotect가false를 반환합니다. Salt나Passphrase를 바꾸면 기존 값을 복호화할 수 없습니다.
비밀번호 해시 (PBKDF2-HMAC-SHA512)
services.AddPasswordHasher();
var stored = hasher.Hash(password); // pbkdf2-sha512$210000$...$...
var isValid = hasher.Verify(password, stored);
if (isValid && hasher.NeedsRehash(stored))
{
// 반복 횟수 정책이 올라갔다면 로그인 성공 시점에 다시 저장
}
반복 횟수가 해시 문자열에 포함되므로, 정책을 올려도 기존 해시를 그대로 검증할 수 있습니다.
해시 · 인코딩
HashText.Sha256Hex("text"); // 체크섬 · 캐시 키 · 조회용 해시
HashText.HmacSha256Hex(payload, secretKey); // 웹훅 서명
HashText.FixedTimeEquals(signature, expected); // 타이밍 공격 방지 비교
Base64UrlText.EncodeText("한글도 안전합니다");
Base64UrlText.TryDecode(token, out var bytes);
HashText 는 비밀번호 저장에 사용하지 않습니다. 비밀번호는 반드시 IPasswordHasher 를 사용하세요.
Mail — SMTP 발송
using GN2.Common.Library.Mail;
services.AddSmtpEmailSender(options => configuration.GetSection("Smtp").Bind(options));
await sender.SendHtmlAsync("user@example.com", "가입을 환영합니다", html, cancellationToken);
await sender.SendAsync(new EmailMessage
{
Subject = "월간 리포트",
To = new[] { "user@example.com" },
Bcc = new[] { "archive@example.com" },
HtmlBody = html,
TextBody = text,
}, cancellationToken);
SmtpConfiguration 은 GN2.Core.Configurations 에 있습니다. 로그에는 수신자 수와 결과만 남고 주소·본문은 남지 않습니다.
Text — HTML 헬퍼
using GN2.Common.Library.Text;
var summary = HtmlText.Summarize(article.Content, 120);
var thumbnail = HtmlText.ExtractFirstImageSource(article.Content);
var plain = HtmlText.StripTags(article.Content);
var body = HtmlText.ToHtmlLineBreaks(comment.Content);
정규식 기반이므로 편집기 산출물 같은 단순한 마크업을 대상으로 합니다. StripTags 는 요약·색인용이며 XSS 방어용 새니타이저가 아닙니다.
2.x 에서 올라올 때
| 2.x | 3.0.0 |
|---|---|
CryptoHelper.AES256.Encrypt/Decrypt |
ISecretProtector.Protect/Unprotect (저장 형식 변경, 이전 값 복호화 불가) |
CryptoHelper.SHA512.Encrypt/ValidateCheck |
IPasswordHasher.Hash/Verify (저장 형식 변경, 이전 해시 검증 불가) |
CryptoHelper.SHA256.Encrypt |
HashText.Sha256Hex (비밀번호 용도로는 사용 금지) |
CryptoHelper.SaltAdd/SaltRemove, Base64Handler |
Base64UrlText |
StartupHelper.GetConfiguration(args) |
builder.Configuration |
StartupHelper.RegisterAllowedCors(hosts) |
ASP.NET Core 기본 services.AddCors(...) |
ServiceLocator.Resolve<T>() / AddLocatorProvider() |
생성자 주입 |
HtmlTagHelper |
HtmlText (뷰 비교 헬퍼와 YouTube 추출은 제거) |
ActionBridgeEngine, ObjectLinkerEngine |
ActionBridge, ObjectLinker (네임스페이스 Bridge, Mapping) |
CORS 는 프레임워크 기본 API 로 대체합니다.
services.AddCors(options => options.AddDefaultPolicy(policy =>
policy.WithOrigins(allowedHosts).AllowAnyHeader().AllowAnyMethod()));
사용 시 주의
ActionBridge와ObjectLinker는 런타임 리플렉션을 사용하므로 트리밍 · Native AOT 경로에서는 사용하지 않습니다.Security·Mail·Text는 리플렉션을 사용하지 않습니다.AddSecretProtector·AddPasswordHasher는 구성 오류를 등록 시점에 예외로 알립니다.Passphrase같은 비밀 값은appsettings.json이 아니라 환경 변수나 보안 저장소에서 주입하세요.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- GN2.Core (>= 3.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Options (>= 10.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 102 | 8/22/2026 |
| 2.1.0 | 142 | 3/11/2026 |
| 2.0.6 | 123 | 3/2/2026 |
| 2.0.5 | 120 | 3/2/2026 |
| 2.0.4 | 118 | 2/23/2026 |
| 2.0.3 | 166 | 2/1/2026 |
| 2.0.2 | 137 | 1/11/2026 |
| 2.0.1 | 134 | 1/11/2026 |
| 2.0.0 | 127 | 1/11/2026 |
| 1.2.1 | 277 | 10/20/2025 |
| 1.2.0 | 231 | 10/20/2025 |
| 1.1.9 | 327 | 10/19/2025 |
| 1.1.8 | 250 | 10/10/2025 |
| 1.1.7 | 351 | 10/2/2025 |
| 1.1.6 | 224 | 9/6/2025 |
| 1.1.5 | 283 | 9/5/2025 |
| 1.1.4 | 321 | 8/30/2025 |
| 1.1.3 | 239 | 8/21/2025 |
| 1.1.2 | 215 | 8/21/2025 |
| 1.1.1 | 176 | 8/16/2025 |
3.0.0 (호환성 없는 변경 포함)
이번 버전은 패키지를 "범용 유틸리티" 로 다시 정의하면서 저장 형식과 공개 API 가 모두 바뀌었습니다. 2.x 에서 올릴 때는 아래 내용을 반드시 확인하세요.
보안 (중요)
- 암호화를 AES-256-GCM 인증 암호화로 교체했습니다. 메시지마다 임의 nonce 를 사용하고 인증 태그로 변조를 감지합니다. 기존 AESHandler / AES128Handler / AES256Handler 는 CBC 고정 IV(비밀키 길이를 솔트로 사용)에 무결성 검증이 없어 제거했습니다.
- 비밀번호 해시를 PBKDF2-HMAC-SHA512 210,000회(OWASP 권장)로 교체했습니다. 무염 SHA-256 해시로 비밀번호를 검증하던 SHA256Handler.ValidateCheck 와 1,000회 반복이던 SHA512Handler 를 제거했습니다.
- 문자로 치환하던 SaltAdd / SaltRemove 를 표준 Base64Url 인코딩으로 대체했습니다.
- 2.x 형식으로 저장된 암호문과 해시는 3.0.0 에서 복호화 · 검증되지 않습니다. 비밀번호는 다음 로그인 시 재해시하고, 암호문은 이전 버전으로 복호화한 뒤 다시 저장해야 합니다.
- 전역 가변 상태였던 CryptoHelper.ConfigureSecret 을 제거했습니다. 이제 ISecretProtector / IPasswordHasher 를 DI 로 주입받습니다.
제거
- ServiceLocator 와 AddLocatorProvider — 서비스 로케이터는 안티패턴이며, AddLocatorProvider 는 BuildServiceProvider 로 컨테이너를 하나 더 만들어 싱글턴이 두 번 생성되는 버그가 있었습니다.
- StartupHelper.GetConfiguration — 호스트 빌더가 이미 수행하는 일을 중복 구현하면서 인자로 받은 builder 를 무시했습니다. builder.Configuration 을 사용하세요.
- RegisterAllowedCors — 레거시 Microsoft.AspNetCore.Cors 2.x 를 참조하게 만들고, 허용 목록이 비면 AllowAnyOrigin 으로 열리는 문제가 있었습니다. ASP.NET Core 기본 AddCors 를 직접 사용하세요.
- HtmlTagHelper 의 ValueCompare / ValueContain — 뷰 전용 헬퍼이며 ValueContain(string) 은 비교 방향이 뒤바뀐 버그가 있었습니다.
- GetYoutubeURLFromTags — 정규식이 잘못돼 그룹당 값이 중복 수집됐습니다.
정확성
- ObjectLinker 의 매핑 캐시가 static 이어서, 서로 다른 컨테이너가 같은 타입 쌍을 다르게 구성하면 먼저 만들어진 규칙이 계속 사용되던 문제를 수정했습니다. 캐시는 이제 인스턴스 단위입니다.
- ObjectLinker 가 매핑할 때마다 속성 목록을 선형 탐색하던 것을, 타입 쌍마다 한 번 계산하는 매핑 계획으로 바꿨습니다.
- ForMember 대상이 존재하지 않거나 쓰기 불가능하면 첫 매핑 시점이 아니라 구성 시점에 예외를 던집니다. (bool)(object) 변환이 섞인 식도 인식합니다.
- ActionBridge 가 호출마다 MethodInfo.Invoke 로 핸들러를 호출하던 것을, 요청 타입별 강타입 디스패처 캐시로 바꿨습니다.
- 한 요청에 핸들러가 둘 이상 발견되면 조용히 마지막 등록이 이기는 대신 등록 시점에 예외를 던집니다. AddActionBridge 를 여러 번 호출해도 중복 등록되지 않습니다.
- SmtpEmailSender 가 수신자 주소와 본문 전체를 Information 수준으로 기록하던 문제를 수정했습니다(개인정보 유출). 이제 수신자 수만 남깁니다.
- SmtpEmailSender 가 SmtpClient 하나를 재사용하고 동기 Send 를 async 메서드에서 호출하던 문제를 수정했습니다. 발송마다 클라이언트를 만들고 SendMailAsync 와 CancellationToken 을 사용합니다.
- 정규식에 타임아웃과 소스 생성기를 적용했습니다.
기능
- IBridgeBehavior 파이프라인을 추가했습니다. AddOpenBehavior(typeof(LoggingBehavior<,>)) 로 모든 요청에 횡단 관심사를 적용할 수 있습니다.
- ObjectLinker 에 MapMany, Ignore, 값 타입 T 에서 T? 로의 규약 매핑을 추가했습니다.
- EmailMessage 로 다중 수신자 · 참조 · 숨은참조 · 회신 주소 · HTML/평문 본문을 지원합니다.
- HtmlText.Summarize 로 목록 미리보기 문구를 만들 수 있습니다.
의존성
- Microsoft.AspNetCore.Cors, Microsoft.Extensions.Configuration(및 CommandLine / EnvironmentVariables / Json), Microsoft.Extensions.Hosting.Abstractions, Microsoft.Extensions.DependencyInjection 참조를 제거했습니다.
- 남은 의존성은 GN2.Core 와 Microsoft.Extensions 추상화(DependencyInjection.Abstractions, Logging.Abstractions, Options) 뿐입니다.
전체 변경 내역: https://github.com/gn2studio/GN2/releases