<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>C# Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<atom:link href="https://lycos7560.com/category/c/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>생각의 흐름을 타고 다니며 만드는 블로그</description>
	<lastBuildDate>Tue, 25 Nov 2025 15:17:43 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://lycos7560.com/wp-content/uploads/2022/11/cropped-cropped-cropped-log-1-150x150-1-80x80.png</url>
	<title>C# Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>C# Switch 식(Expression)</title>
		<link>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/</link>
					<comments>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 11 Nov 2025 15:02:40 +0000</pubDate>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[!=]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET 5]]></category>
		<category><![CDATA[.NET 6]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[>=]]></category>
		<category><![CDATA[<=]]></category>
		<category><![CDATA[==]]></category>
		<category><![CDATA[2019]]></category>
		<category><![CDATA[2020]]></category>
		<category><![CDATA[access]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[Admin]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Arrow Operator]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Boolean]]></category>
		<category><![CDATA[Branch]]></category>
		<category><![CDATA[Branching]]></category>
		<category><![CDATA[C#7]]></category>
		<category><![CDATA[C#8]]></category>
		<category><![CDATA[C#9]]></category>
		<category><![CDATA[Case]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[Clean Code]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Comparison]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Concise]]></category>
		<category><![CDATA[Condition]]></category>
		<category><![CDATA[Conditional]]></category>
		<category><![CDATA[Control]]></category>
		<category><![CDATA[Control Flow]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[Declarative]]></category>
		<category><![CDATA[Declarative Style]]></category>
		<category><![CDATA[Default]]></category>
		<category><![CDATA[delegate]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[Developer Guide]]></category>
		<category><![CDATA[Docs]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[Education]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[Error Handling]]></category>
		<category><![CDATA[Example]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[Execution]]></category>
		<category><![CDATA[Expression]]></category>
		<category><![CDATA[Expression Body]]></category>
		<category><![CDATA[Expression Tree]]></category>
		<category><![CDATA[False]]></category>
		<category><![CDATA[Feature]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[Function Pointer]]></category>
		<category><![CDATA[Functional]]></category>
		<category><![CDATA[Functional Programming]]></category>
		<category><![CDATA[Grammar]]></category>
		<category><![CDATA[Guide]]></category>
		<category><![CDATA[History]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Imperative]]></category>
		<category><![CDATA[Intuitive]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[Language Evolution]]></category>
		<category><![CDATA[Language Feature]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Library]]></category>
		<category><![CDATA[Logical Pattern]]></category>
		<category><![CDATA[Maintainable]]></category>
		<category><![CDATA[Mapping]]></category>
		<category><![CDATA[method]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Modern C#]]></category>
		<category><![CDATA[Modern Programming]]></category>
		<category><![CDATA[New Feature]]></category>
		<category><![CDATA[November]]></category>
		<category><![CDATA[Null]]></category>
		<category><![CDATA[Null Check]]></category>
		<category><![CDATA[Nullable]]></category>
		<category><![CDATA[Object]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Operator]]></category>
		<category><![CDATA[Pattern Matching]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Property]]></category>
		<category><![CDATA[Property Pattern]]></category>
		<category><![CDATA[Range]]></category>
		<category><![CDATA[Readable]]></category>
		<category><![CDATA[Refactor]]></category>
		<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[Relational Pattern]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[Return Value]]></category>
		<category><![CDATA[Role]]></category>
		<category><![CDATA[Running]]></category>
		<category><![CDATA[Runtime]]></category>
		<category><![CDATA[Sample Code]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[September]]></category>
		<category><![CDATA[Simplify]]></category>
		<category><![CDATA[Snippet]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[State]]></category>
		<category><![CDATA[Statement]]></category>
		<category><![CDATA[Stopped]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Switch Action]]></category>
		<category><![CDATA[Switch Branch]]></category>
		<category><![CDATA[Switch Case]]></category>
		<category><![CDATA[Switch Clean]]></category>
		<category><![CDATA[Switch Default]]></category>
		<category><![CDATA[Switch Delegate]]></category>
		<category><![CDATA[Switch Docs]]></category>
		<category><![CDATA[Switch Evolution]]></category>
		<category><![CDATA[Switch Example]]></category>
		<category><![CDATA[Switch Expression]]></category>
		<category><![CDATA[Switch Expression Action]]></category>
		<category><![CDATA[Switch Expression C#]]></category>
		<category><![CDATA[Switch Expression Clean]]></category>
		<category><![CDATA[Switch Expression Default]]></category>
		<category><![CDATA[Switch Expression Delegate]]></category>
		<category><![CDATA[Switch Expression Docs]]></category>
		<category><![CDATA[Switch Expression Education]]></category>
		<category><![CDATA[Switch Expression Evolution]]></category>
		<category><![CDATA[Switch Expression Example]]></category>
		<category><![CDATA[Switch Expression Guide]]></category>
		<category><![CDATA[Switch Expression History]]></category>
		<category><![CDATA[Switch Expression Lambda]]></category>
		<category><![CDATA[Switch Expression Learning]]></category>
		<category><![CDATA[Switch Expression Logical]]></category>
		<category><![CDATA[Switch Expression Migration]]></category>
		<category><![CDATA[Switch Expression Modern]]></category>
		<category><![CDATA[Switch Expression Null]]></category>
		<category><![CDATA[Switch Expression Pattern]]></category>
		<category><![CDATA[Switch Expression Property]]></category>
		<category><![CDATA[Switch Expression Relational]]></category>
		<category><![CDATA[Switch Expression Sample]]></category>
		<category><![CDATA[Switch Expression Simplify]]></category>
		<category><![CDATA[Switch Expression Snippet]]></category>
		<category><![CDATA[Switch Expression Study]]></category>
		<category><![CDATA[Switch Expression Tuple]]></category>
		<category><![CDATA[Switch Expression Tutorial]]></category>
		<category><![CDATA[Switch Expression Upgrade]]></category>
		<category><![CDATA[Switch Expression Version]]></category>
		<category><![CDATA[Switch Expression Wildcard]]></category>
		<category><![CDATA[Switch Feature]]></category>
		<category><![CDATA[Switch Flow]]></category>
		<category><![CDATA[Switch Function]]></category>
		<category><![CDATA[Switch Guide]]></category>
		<category><![CDATA[Switch Keyword]]></category>
		<category><![CDATA[Switch Lambda]]></category>
		<category><![CDATA[Switch Logical]]></category>
		<category><![CDATA[Switch Mapping]]></category>
		<category><![CDATA[Switch Modern]]></category>
		<category><![CDATA[Switch Null]]></category>
		<category><![CDATA[Switch Operator]]></category>
		<category><![CDATA[Switch Pattern]]></category>
		<category><![CDATA[Switch Property]]></category>
		<category><![CDATA[Switch Relational]]></category>
		<category><![CDATA[Switch Simplify]]></category>
		<category><![CDATA[Switch Statement]]></category>
		<category><![CDATA[Switch Syntax]]></category>
		<category><![CDATA[Switch Tuple]]></category>
		<category><![CDATA[Switch Tutorial]]></category>
		<category><![CDATA[Switch Wildcard]]></category>
		<category><![CDATA[Syntax]]></category>
		<category><![CDATA[System]]></category>
		<category><![CDATA[True]]></category>
		<category><![CDATA[tuple]]></category>
		<category><![CDATA[Tuple Pattern]]></category>
		<category><![CDATA[TUTORIAL]]></category>
		<category><![CDATA[Upgrade]]></category>
		<category><![CDATA[User]]></category>
		<category><![CDATA[Value]]></category>
		<category><![CDATA[Variable]]></category>
		<category><![CDATA[version]]></category>
		<category><![CDATA[Visual studio]]></category>
		<category><![CDATA[VS Code]]></category>
		<category><![CDATA[Wildcard]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40362</guid>

					<description><![CDATA[<p>C# Switch 식(Expression) C# 8.0(2019년 9월)부터 도입된 Switch 식(Switch Expression)은 기존의 switch-case 문을 획기적으로 줄여주는 강력한 기능 람다 표현식(=>)을 쓰는 것처럼 간결하고 직관적인 분기 처리가 가능 기존 방식 (Statement) 새로운 방식 (Expression) 강력한 활용 패턴 C# 9.0(2020년 11월)부터 Switch 식이 단순 매핑이 아니라 패턴 매칭(Pattern Matching)과 결합됨 범위 비교 (Relational Pattern) C# 9.0부터는 when 키워드 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/">C# Switch 식(Expression)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-1bf84b0a      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#c-switch-식expression" class="uagb-toc-link__trigger">C# Switch 식(Expression)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#기존-방식-statement" class="uagb-toc-link__trigger">기존 방식 (Statement)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#새로운-방식-expression" class="uagb-toc-link__trigger">새로운 방식 (Expression)</a></li></ul></li><li class="uagb-toc__list"><a href="#강력한-활용-패턴" class="uagb-toc-link__trigger">강력한 활용 패턴</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#범위-비교-relational-pattern" class="uagb-toc-link__trigger">범위 비교 (Relational Pattern)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#여러-변수-동시에-비교-tuple-pattern" class="uagb-toc-link__trigger">여러 변수 동시에 비교 (Tuple Pattern)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#객체-속성-비교-property-pattern" class="uagb-toc-link__trigger">객체 속성 비교 (Property Pattern)</a></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#리턴값이-없는-메서드action-실행" class="uagb-toc-link__trigger">리턴값이 없는 메서드(Action) 실행</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">C# Switch 식(Expression)</h2>



<p>C# 8.0(2019년 9월)부터 도입된 <strong>Switch 식(Switch Expression)</strong>은 기존의 <code><strong>switch-case</strong></code> 문을 획기적으로 줄여주는 강력한 기능</p>



<p>람다 표현식(<code>=></code>)을 쓰는 것처럼 간결하고 직관적인 분기 처리가 가능</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">기존 방식 (Statement)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public string GetGrade(int score)
{
    string grade;
    switch (score)
    {
        case 90:
            grade = "A";
            break;
        case 80:
            grade = "B";
            break;
        default:
            grade = "C";
            break;
    }
    return grade;
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">새로운 방식 (Expression)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public string GetGrade(int score) => score switch
{
    90 => "A",
    80 => "B",
    _  => "C"
};


또는 다른 범위 방식으로 (결과는 다름) 
score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _     => "D"
};
</pre>



<ul class="wp-block-list">
<li><strong>위치 이동:</strong> <code>switch</code> 키워드가 변수 <strong>뒤</strong>로 이동 (<code>score switch</code>)</li>



<li><strong>화살표 사용:</strong> <code>case ... :</code> 대신 람다 화살표 <code>=></code>를 사용</li>



<li><strong>Default 처리:</strong> <code>default:</code> 대신 와일드카드 <code>_</code>를 사용</li>



<li><strong>식(Expression):</strong> 그 자체가 &#8216;값&#8217;을 반환하므로 메서드의 리턴값으로 바로 사용할 수 있음</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">강력한 활용 패턴</h2>



<p>C# 9.0(2020년 11월)부터 Switch 식이 단순 매핑이 아니라 <strong>패턴 매칭(Pattern Matching)</strong>과 결합됨</p>



<ul class="wp-block-list">
<li><strong>Switch Expression 자체는 C# 8에서 처음 도입</strong></li>



<li><strong>Pattern Matching 자체는 C# 7부터 지원</strong></li>



<li><strong>Relational Pattern, Logical Pattern 등이 C# 9에서 추가</strong></li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">범위 비교 (Relational Pattern)</h3>



<p>C# 9.0부터는 <code>when</code> 키워드 없이도 관계 연산자를 직접 사용</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">int temperature = 28;

string weather = temperature switch
{
    >= 30 => "폭염",
    >= 20 => "쾌적함",
    >= 10 => "쌀쌀함",
    _     => "추움"
};</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">여러 변수 동시에 비교 (Tuple Pattern)</h3>



<p>두 개 이상의 상태를 조합해서 분기해야 할 때, <code>if-else</code> 중첩 없이 튜플 패턴으로 깔끔하게 해결</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">string state = "Running";
bool hasError = true;

// (상태, 에러여부)를 한 번에 검사
string message = (state, hasError) switch
{
    ("Running", false) => "정상 작동 중",
    ("Running", true)  => "작동 중이나 오류 발생",
    ("Stopped", _)     => "시스템 중지됨", // _는 true/false 상관없음
    _                  => "알 수 없는 상태"
};</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">객체 속성 비교 (Property Pattern)</h3>



<p>객체 내부의 특정 프로퍼티 값에 따라 분기할 수도 있음 </p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var user = new User { Role = "Admin", IsDeleted = false };

string accessLevel = user switch
{
    { Role: "Admin", IsDeleted: false } => "관리자 권한",
    { Role: "User", IsDeleted: false }  => "일반 사용자",
    { IsDeleted: true }                 => "삭제된 계정",
    _                                   => "접근 불가" // null 포함
};

// 객체가 null인 경우도 자동으로 _</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">리턴값이 없는 메서드(Action) 실행</h2>



<p><code>Action</code> 델리게이트를 반환받아 즉시 실행</p>



<p>switch expression은 &#8220;값&#8221;을 반환하므로 델리게이트(함수 포인터)를 반환하는 것도 가능</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">int command = 1;

// 1. 각 조건에 맞는 Action(행동)을 선택하고
// 2. 마지막의 ()로 즉시 실행합니다.
(command switch
{
    1 => (Action)(() => SaveData()),
    2 => () => LoadData(),
    _ => () => Console.WriteLine("대기")
})(); 
// 마지막 ()는 "반환된 델리게이트 실행"</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/">C# Switch 식(Expression)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>IServiceScopeFactory 인터페이스</title>
		<link>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/</link>
					<comments>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 25 Sep 2025 20:27:40 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40295</guid>

					<description><![CDATA[<p>IServiceScopeFactory 란? https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0 IServiceScopeFactory 는 ASP.NET Core의 의존성 주입(DI) 컨테이너에서 새로운 서비스 범위(Scope) 를 만들기 위한 팩토리 인터페이스입니다. 기본적으로 DI 컨테이너는 Singleton / Scoped / Transient 세 가지 생명주기를 제공합니다. 일반적으로 Scoped 서비스는 HTTP 요청(Request)마다 생성되고, 요청이 끝나면 해제됩니다. 하지만 BackgroundService, Singleton 클래스 안에서는 Scoped 서비스를 직접 DI 받을 수 없습니다. 이를 해결하기 위하여 IServiceScopeFactory [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/">IServiceScopeFactory 인터페이스</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-091bc55d      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#iservicescopefactory-란" class="uagb-toc-link__trigger">IServiceScopeFactory 란?</a><li class="uagb-toc__list"><a href="#사용법" class="uagb-toc-link__trigger">사용법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-기본적인-사용법" class="uagb-toc-link__trigger">1. 기본적인 사용법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-backgroundservice에서-dbcontext-사용하기" class="uagb-toc-link__trigger">2. BackgroundService에서 DbContext 사용하기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-scoped-서비스-임시-실행" class="uagb-toc-link__trigger">3. Scoped 서비스 임시 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-여러-scoped-서비스-조합" class="uagb-toc-link__trigger">4. 여러 Scoped 서비스 조합</a></li></ul></li><li class="uagb-toc__list"><a href="#정리" class="uagb-toc-link__trigger">정리</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">IServiceScopeFactory 란?</h2>



<p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0">https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory?view=net-8.0</a></p>



<p><code>IServiceScopeFactory</code> 는 ASP.NET Core의 <strong>의존성 주입(DI) 컨테이너</strong>에서 새로운 <strong>서비스 범위(Scope)</strong> 를 만들기 위한 팩토리 인터페이스입니다.</p>



<p>기본적으로 DI 컨테이너는 <strong>Singleton / Scoped / Transient</strong> 세 가지 생명주기를 제공합니다.</p>



<p>일반적으로 Scoped 서비스는 HTTP 요청(Request)마다 생성되고, 요청이 끝나면 해제됩니다.</p>



<p>하지만 <code>BackgroundService</code>, <code>Singleton</code> 클래스 안에서는 Scoped 서비스를 직접 DI 받을 수 없습니다. </p>



<p>이를 해결하기 위하여 <code>IServiceScopeFactory</code> 를 이용해 <strong>임시 Scope를 만들어서 Scoped 서비스를 안전하게 사용</strong>합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>구분</th><th>Singleton</th><th>Scoped</th><th>Transient</th></tr></thead><tbody><tr><td><strong>인스턴스 생성 시점</strong></td><td>애플리케이션 시작 시 한 번</td><td>HTTP 요청 시작 시 한 번</td><td>의존성 주입 요청 시마다</td></tr><tr><td><strong>인스턴스 수명</strong></td><td>애플리케이션 전체 수명</td><td>HTTP 요청 수명</td><td>가장 짧은 수명</td></tr><tr><td><strong>공유 범위</strong></td><td>전체 애플리케이션에서 단일 인스턴스 공유</td><td>동일 HTTP 요청 내에서 동일 인스턴스 공유</td><td>매번 새로운 인스턴스 생성</td></tr><tr><td><strong>사용 사례</strong></td><td>&#8211; 캐시 서비스<br>&#8211; 설정 관리자<br>&#8211; 로깅 서비스</td><td>&#8211; DB Context<br>&#8211; 사용자 세션<br>&#8211; 트랜잭션 관리</td><td>&#8211; 경량 서비스<br>&#8211; 상태 없는 헬퍼<br>&#8211; 일회성 작업</td></tr><tr><td><strong>주의사항</strong></td><td>상태 저장 시 thread-safe 고려 필요</td><td>요청 간 상태 공유 불가</td><td>빈번한 생성으로 성능 영향 가능성</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">사용법</h2>



<h3 class="wp-block-heading">1. 기본적인 사용법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using (var scope = scopeFactory.CreateScope())
{
    var myService = scope.ServiceProvider.GetRequiredService&lt;MyScopedService>();
    myService.DoSomething();
}
// scope.Dispose()가 호출되면서 MyScopedService도 정리됨
</pre>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. BackgroundService에서 DbContext 사용하기</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class MyWorker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public MyWorker(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService&lt;MyDbContext>();

            var users = await db.Users.ToListAsync(stoppingToken);
            Console.WriteLine($"현재 사용자 수: {users.Count}");

            await Task.Delay(5000, stoppingToken);
        }
    }
}

// MyDbContext 가 Scoped 로 등록되어 있어도 Worker에서 안전하게 사용 가능.</pre>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. Scoped 서비스 임시 실행</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class ReportGenerator
{
    private readonly IServiceScopeFactory _scopeFactory;

    public ReportGenerator(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public void Generate()
    {
        using var scope = _scopeFactory.CreateScope();
        var service = scope.ServiceProvider.GetRequiredService&lt;ReportService>();

        service.CreateDailyReport();
    }
}

// ReportService 가 Scoped 로 등록되어 있어도 IServiceScopeFactory 를 사용해 특정 시점에만 실행 가능.</pre>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4. 여러 Scoped 서비스 조합</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class CleanupJob
{
    private readonly IServiceScopeFactory _scopeFactory;

    public CleanupJob(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task RunAsync()
    {
        using var scope = _scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService&lt;MyDbContext>();
        var emailService = scope.ServiceProvider.GetRequiredService&lt;EmailService>();

        var expiredUsers = db.Users.Where(u => u.IsExpired).ToList();
        db.Users.RemoveRange(expiredUsers);
        await db.SaveChangesAsync();

        await emailService.SendCleanupReport(expiredUsers.Count);
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">정리</h2>



<ul class="wp-block-list">
<li><code>IServiceScopeFactory</code> 는 <strong>Scoped 서비스의 생명주기를 직접 제어</strong>할 수 있게 해줍니다.</li>



<li>주로 <strong>Singleton 객체나 BackgroundService</strong> 에서 Scoped 서비스를 안전하게 사용하기 위해 필요합니다.</li>



<li><code>CreateScope()</code> 로 범위를 열고, 작업이 끝나면 자동으로 Dispose 되어 메모리 누수 없이 관리됩니다.</li>
</ul>



<p></p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/">IServiceScopeFactory 인터페이스</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/iservicescopefactory-%ec%9d%b8%ed%84%b0%ed%8e%98%ec%9d%b4%ec%8a%a4/40295/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 07:45:23 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[Account]]></category>
		<category><![CDATA[AccountManagement]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Blazor]]></category>
		<category><![CDATA[Callback]]></category>
		<category><![CDATA[CD]]></category>
		<category><![CDATA[Challenge]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CICD]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[ClaimsPrincipal]]></category>
		<category><![CDATA[ClientSide]]></category>
		<category><![CDATA[CloudComputing]]></category>
		<category><![CDATA[CloudNative]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Cookie]]></category>
		<category><![CDATA[Core]]></category>
		<category><![CDATA[Cryptography]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DataTransferObject]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[DTO]]></category>
		<category><![CDATA[EmailConfirmation]]></category>
		<category><![CDATA[EntityFramework]]></category>
		<category><![CDATA[ErrorHandling]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[ExternalLogin]]></category>
		<category><![CDATA[ExternalProvider]]></category>
		<category><![CDATA[Frontend]]></category>
		<category><![CDATA[FullStack]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[GitLab]]></category>
		<category><![CDATA[GoogleAuth]]></category>
		<category><![CDATA[GoogleCloud]]></category>
		<category><![CDATA[GoogleLogin]]></category>
		<category><![CDATA[HTTP302]]></category>
		<category><![CDATA[HttpGet]]></category>
		<category><![CDATA[HttpPost]]></category>
		<category><![CDATA[HttpRequest]]></category>
		<category><![CDATA[HttpResponse]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Identity]]></category>
		<category><![CDATA[IIS]]></category>
		<category><![CDATA[JSONWebToken]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Log]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Login]]></category>
		<category><![CDATA[Logout]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[MicrosoftIdentity]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[NoSQL]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[OAuth2.0]]></category>
		<category><![CDATA[OpenID]]></category>
		<category><![CDATA[OpenIDConnect]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[password]]></category>
		<category><![CDATA[Passwordless]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[Profile]]></category>
		<category><![CDATA[QueryString]]></category>
		<category><![CDATA[Redirect]]></category>
		<category><![CDATA[RedirectResponse]]></category>
		<category><![CDATA[Register]]></category>
		<category><![CDATA[ResetPassword]]></category>
		<category><![CDATA[RESTfulAPI]]></category>
		<category><![CDATA[Route]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SecurityToken]]></category>
		<category><![CDATA[ServerSide]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[SignInManager]]></category>
		<category><![CDATA[SignUp]]></category>
		<category><![CDATA[SinglePageApplication]]></category>
		<category><![CDATA[SocialLogin]]></category>
		<category><![CDATA[SPA]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Startup]]></category>
		<category><![CDATA[StateManagement]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[TryCatch]]></category>
		<category><![CDATA[TwoFactor]]></category>
		<category><![CDATA[URI]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[UserDB]]></category>
		<category><![CDATA[UserManagement]]></category>
		<category><![CDATA[UserManager]]></category>
		<category><![CDATA[UserProfile]]></category>
		<category><![CDATA[UserStore]]></category>
		<category><![CDATA[VisualStudio]]></category>
		<category><![CDATA[VSCode]]></category>
		<category><![CDATA[WebAPI]]></category>
		<category><![CDATA[WebApp]]></category>
		<category><![CDATA[WebApplication]]></category>
		<category><![CDATA[WebDevelopment]]></category>
		<category><![CDATA[WebSecurity]]></category>
		<category><![CDATA[WebServer]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40245</guid>

					<description><![CDATA[<p>🔥 ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석 1️⃣ 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭 Navigation.NavigateTo(..., forceLoad: true) Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(api/auth/Challenge/Google...)로 GET 요청보낸다. 2️⃣ 백엔드의 인증 시작 (AuthController &#8211; Challenge) 3️⃣ 외부 공급자 인증 (Google) 사용자는 Google 로그인 페이지로 이동 4️⃣ 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback) [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</h2>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-cbc30617      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-identity를-활용한-구글-로그인oauth-전체-흐름-분석" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-사용자가-google로-로그인-버튼을-클릭" class="uagb-toc-link__trigger">1&#x20e3; 사용자가 &quot;Google로 로그인&quot; 버튼을 클릭</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-백엔드의-인증-시작-authcontroller-challenge" class="uagb-toc-link__trigger">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-외부-공급자-인증-google" class="uagb-toc-link__trigger">3&#x20e3; 외부 공급자 인증 (Google)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-백엔드의-콜백-처리-및-로그인회원가입-authcontroller-callback" class="uagb-toc-link__trigger">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-완료" class="uagb-toc-link__trigger">5&#x20e3; 완료</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#6-authcontrollercs" class="uagb-toc-link__trigger">6&#x20e3; AuthController.cs</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1159" height="1920" src="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg" alt="" class="wp-image-40248" srcset="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg 1159w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-181x300.jpg 181w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-768x1272.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-927x1536.jpg 927w" sizes="(max-width: 1159px) 100vw, 1159px" /></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭</h3>



<p><code>Navigation.NavigateTo(..., forceLoad: true)</code></p>



<p>Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(<code>api/auth/Challenge/Google...</code>)로 <strong>GET 요청</strong>보낸다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
private void LoginWithGoogle()
{
    var returnUrl = "/"; // 로그인 성공 후 돌아올 주소
    var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

    // 이 주소로 브라우저가 페이지를 새로고침하며 이동
    Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("Challenge/{provider}")]
public IActionResult Challenge(string provider, string? returnUrl = null)
{
    // ...
    var redirectUrl = Url.Action("Callback", "Auth", ...); // 1. Google이 인증 후 돌아올 우리 API 주소(Callback)를 지정
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); // 2. 인증 요청에 필요한 속성 구성

    return Challenge(properties, provider); // 3. Google 로그인 페이지로 리디렉션하는 응답 생성
}</pre>



<ul class="wp-block-list">
<li><code>AuthController.cs</code>의 <code>Challenge</code> 메서드가 실행됩니다. <br><code>provider</code> 매개변수에는 &#8220;Google&#8221;이 전달</li>



<li><code>_signInManager</code>는 ASP.NET Core Identity의 핵심 기능으로, 외부 로그인 과정을 도와줌</li>



<li><code>return Challenge(properties, provider);</code>는 브라우저에게 <strong>HTTP 302 Redirect</strong> 응답을 보냄<br>이 응답 헤더에는 사용자가 이동해야 할 <strong>Google의 로그인 페이지 주소</strong>가 포함</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 외부 공급자 인증 (Google)</h3>



<p><strong>사용자는 Google 로그인 페이지로 이동</strong></p>



<ul class="wp-block-list">
<li>사용자는 자신의 Google 계정으로 로그인하고, 우리 애플리케이션이 요청하는 정보(이메일, 프로필 등)에 대한 접근 권한을 허용</li>



<li>인증이 성공적으로 완료되면, Google은 2단계에서 백엔드가 지정했던 <strong>콜백 URL</strong>(<code>api/Auth/Callback</code>)로 사용자를 다시 리디렉션<br>이때 인증 코드 또는 토큰과 함께 사용자를 보냄</li>
</ul>



<ol start="1" class="wp-block-list"></ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</h3>



<p><strong>Google이 사용자를 우리 백엔드의 <code>Callback</code> 엔드포인트로 돌려보냄</strong></p>



<p><code>AuthController.cs</code>의 <code>Callback</code> (라우팅 경로: <code>externalLogin</code>) 메서드가 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("externalLogin")]
public async Task&lt;IActionResult> Callback(...)
{
    // 1. Google이 보내준 정보로 외부 로그인 정보를 가져옴
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null) { /* 에러 처리 */ }

    // 2. 이 외부 정보(공급자, 키)로 우리 DB에 연결된 사용자가 있는지 확인하고 로그인 시도
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, ...);

    if (result.Succeeded)
    {
        // 3a. 성공! 이미 가입하고 연동된 사용자. 로그인 처리 후 returnUrl로 리디렉션
        return LocalRedirect(returnUrl);
    }
    else
    {
        // 3b. 실패. 신규 사용자이거나, 이메일은 같지만 연동되지 않은 사용자.
        return await HandleUserCreationOrLinking(info, returnUrl);
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>HandleUserCreationOrLinking</code> 메서드는 다음 두 가지 시나리오를 처리</p>



<ul class="wp-block-list">
<li><strong>시나리오 A: 이메일은 존재하지만 소셜 연동이 안 된 경우 (<code>LinkExistingUser</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일로 우리 DB에서 사용자를 찾습니다. (<code>_userManager.FindByEmailAsync</code>)</li>



<li>기존 사용자가 있다면, 해당 계정에 이 Google 로그인 정보를 추가로 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>연결 후, 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>



<li><strong>시나리오 B: 완전히 새로운 사용자인 경우 (<code>CreateNewUserAsync</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일과 이름으로 새로운 <code>ApplicationUser</code> 객체를 만듭니다. 이때 <code>EmailConfirmed</code>는 <code>true</code>로 설정합니다. (소셜 로그인은 이미 이메일이 인증된 것으로 간주)</li>



<li><code>_userManager.CreateAsync(user)</code>로 DB에 새 사용자를 저장합니다.</li>



<li>새로 생성된 사용자 계정에 Google 로그인 정보를 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>새 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5&#x20e3; 완료</h3>



<p>로그인 또는 회원가입 및 연동이 모두 성공적으로 끝나면, 백엔드는 최종적으로 <code>LocalRedirect(returnUrl)</code>을 통해 사용자를 맨 처음 <code>Login.razor</code>에서 지정했던 주소(<code>/</code>)로 리디렉션합니다. </p>



<p>이제 사용자는 로그인된 상태로 메인 페이지를 보게 됩니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">6&#x20e3; AuthController.cs</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.IdentityModel.Tokens;
using Neco.BaseTemplate.Shared.Data;
using Neco.BaseTemplete.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace Neco.BaseTemplete.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;ApplicationUser> _userManager;
        private readonly SignInManager&lt;ApplicationUser> _signInManager;
        private readonly IUserStore&lt;ApplicationUser> _userStore;
        private readonly IEmailSender&lt;ApplicationUser> _emailSender;
        private readonly ILogger&lt;AuthController> _logger;

        public AuthController(
            UserManager&lt;ApplicationUser> userManager,
            SignInManager&lt;ApplicationUser> signInManager,
            IUserStore&lt;ApplicationUser> userStore,
            IEmailSender&lt;ApplicationUser> emailSender,
            ILogger&lt;AuthController> logger)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _userStore = userStore;
            _emailSender = emailSender;
            _logger = logger;
        }

        #region Basic Authentication

        /// &lt;summary>
        /// Handles user login.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password, RememberMe option)&lt;/param>
        /// &lt;returns>The login result&lt;/returns>
        [HttpPost("login")]
        public async Task&lt;IActionResult> Login([FromBody] LoginUserRequestDTO model)
        {
            try
            {
                var result = await _signInManager.PasswordSignInAsync(
                    model.Email,
                    model.Password,
                    model.RememberMe,
                    lockoutOnFailure: true);

                if (result.Succeeded)
                {
                    _logger.LogInformation("User logged in: {Email}", model.Email);
                    return Ok(new { Success = true });
                }

                if (result.RequiresTwoFactor)
                {
                    return Ok(new { RequiresTwoFactor = true });
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("User account locked out: {Email}", model.Email);
                    return BadRequest("Account locked out");
                }

                return BadRequest("Invalid login attempt");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Login error for {Email}", model.Email);
                return StatusCode(500, "An error occurred");
            }
        }

        /// &lt;summary>
        /// Handles new user registration.
        /// &lt;/summary>
        /// &lt;param name="model">Registration request DTO&lt;/param>
        /// &lt;returns>The registration result&lt;/returns>
        [HttpPost("register")]
        public async Task&lt;IActionResult> Register([FromBody] CreateUserRequestDTO model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_RequestInvalid,
                    Errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
                });
            }

            var user = CreateUser();
            await _userStore.SetUserNameAsync(user, model.Email, CancellationToken.None);
            var emailStore = GetEmailStore();
            await emailStore.SetEmailAsync(user, model.Email, CancellationToken.None);

            var result = await _userManager.CreateAsync(user, model.Password);

            if (!result.Succeeded)
            {
                _logger.LogInformation("User registration failed: {Email}", model.Email);

                // Handle duplicate email error
                if (result.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    return BadRequest(new RegisterUserResponseDTO
                    {
                        Status = RegisterUserResponseDTO.ResponseStatus.Fail_DuplicateEmail,
                    });
                }

                // Handle general Identity errors
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_General,
                    Errors = result.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("User created a new account with a password.");

            // Send email confirmation link
            try
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: For security, it's recommended to configure a fixed base URL
                // from your application's settings rather than relying on the request host.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));
            }
            catch (Exception)
            {
                // Account created successfully but email sending failed.
                return Ok(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Success_EmailWarning,
                    Message = "The account was created, but the verification email sending failed."
                });
            }

            return Ok(new RegisterUserResponseDTO
            {
                Status = RegisterUserResponseDTO.ResponseStatus.Success_NeedConfirmation
            });
        }

        /// &lt;summary>
        /// Resends the email confirmation link.
        /// &lt;/summary>
        /// &lt;param name="model">Email confirmation request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("emailConfirm")]
        public async Task&lt;IActionResult> ResendEmailConfirm([FromBody] RequestEmailConfirmDTO model)
        {
            if (!ModelState.IsValid)
            {
                // Always return a success response for security (to avoid user enumeration).
                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }

            try
            {
                var user = await _userManager.FindByEmailAsync(model.Email);

                // Even if the user doesn't exist or is already confirmed, return a success response for security.
                if (user == null || user.EmailConfirmed)
                {
                    return Ok(new ResponseEmailConfirmDTO
                    {
                        Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                        Message = "The confirmation email has been sent successfully."
                    });
                }

                // Generate and send the email confirmation link
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: It is recommended to use a pre-configured base URL.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));

                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }
            catch (Exception)
            {
                // Mail server or other exception occurred.
                return BadRequest(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Fail,
                    Message = $"An error occurred while processing your request. (Server Error)"
                });
            }
        }

        /// &lt;summary>
        /// Handles password reset requests.
        /// &lt;/summary>
        /// &lt;param name="model">Password reset request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("resetPassword")]
        public async Task&lt;IActionResult> ResetPassword([FromBody] ResetPasswordRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return BadRequest("User not found");
            }

            var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
            var callbackUrl = $"{model.BaseUrl}/Account/ResetPassword?email={model.Email}&amp;token={resetToken}";

            await _emailSender.SendPasswordResetLinkAsync(user, model.Email, callbackUrl);

            return Ok("Password reset link sent");
        }

        /// &lt;summary>
        /// Handles user logout.
        /// &lt;/summary>
        /// &lt;returns>The logout result&lt;/returns>
        [HttpPost("logout")]
        public async Task&lt;IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return Ok("Logged out successfully");
        }

        /// &lt;summary>
        /// Handles the generation of a JWT token.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password)&lt;/param>
        /// &lt;returns>Token information&lt;/returns>
        [HttpPost("token")]
        public async Task&lt;IActionResult> GenerateToken([FromBody] LoginUserRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
            {
                return Unauthorized("Invalid credentials");
            }

            var claims = new[]
            {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.Email, user.Email)
            };

            // WARNING: Do not hard-code the key in production.
            // Move this to a secure configuration file (e.g., appsettings.json)
            // and use a strong, randomly generated key.
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A_Very_Strong_And_Secret_Key_For_JWT_Auth"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: "yourdomain.com",
                audience: "yourdomain.com",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }

        #endregion

        #region External Authentication (OAuth)

        /// &lt;summary>
        /// Initiates a challenge request to an external authentication provider (e.g., Google).
        /// &lt;/summary>
        /// &lt;param name="provider">The name of the authentication provider (e.g., "Google")&lt;/param>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;returns>A challenge result to the external provider&lt;/returns>
        [HttpGet("Challenge/{provider}")]
        public IActionResult Challenge(string provider, string? returnUrl = null)
        {
            _logger.LogInformation("Starting {Provider} login", provider);

            if (string.IsNullOrWhiteSpace(provider))
            {
                return BadRequest(new { error = "Provider not specified." });
            }

            // Normalize and validate returnUrl
            returnUrl = NormalizeReturnUrl(returnUrl);

            // Set the callback URL
            var redirectUrl = Url.Action("Callback", "Auth", new { returnUrl }, Request.Scheme);

            // Configure authentication properties
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

            return Challenge(properties, provider);
        }

        /// &lt;summary>
        /// Handles the callback from an external authentication provider.
        /// &lt;/summary>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;param name="remoteError">Error message from the external provider&lt;/param>
        /// &lt;returns>An appropriate response based on the authentication result&lt;/returns>
        [HttpGet("externalLogin")]
        public async Task&lt;IActionResult> Callback(string? returnUrl = null, string? remoteError = null)
        {
            try
            {
                returnUrl = NormalizeReturnUrl(returnUrl);

                if (!string.IsNullOrEmpty(remoteError))
                {
                    _logger.LogError("External provider error: {RemoteError}", remoteError);
                    return Redirect($"/login?error={Uri.EscapeDataString(remoteError)}");
                }

                var info = await _signInManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    _logger.LogError("Could not retrieve external login info");
                    return Redirect("/login?error=external_login_failed");
                }

                // Attempt to sign in an existing user
                var result = await _signInManager.ExternalLoginSignInAsync(
                    info.LoginProvider,
                    info.ProviderKey,
                    isPersistent: false,
                    bypassTwoFactor: true);

                if (result.Succeeded)
                {
                    var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
                    _logger.LogInformation("User {Email} successfully logged in", user?.Email);
                    return LocalRedirect(returnUrl);
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("Attempt to log in with a locked-out account");
                    return Redirect("/login?error=locked_out");
                }

                // New user or linking an existing user
                return await HandleUserCreationOrLinking(info, returnUrl);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while processing external login");
                return Redirect("/login?error=server_error");
            }
        }

        #endregion

        #region Helper Methods

        /// &lt;summary>
        /// Normalizes and validates the returnUrl.
        /// &lt;/summary>
        private string NormalizeReturnUrl(string? returnUrl)
        {
            if (string.IsNullOrWhiteSpace(returnUrl))
                return "/";

            if (Uri.TryCreate(returnUrl, UriKind.Absolute, out var uri))
                returnUrl = uri.PathAndQuery;

            return Url.IsLocalUrl(returnUrl) ? returnUrl : "/";
        }

        /// &lt;summary>
        /// Handles new user creation or linking an existing user to an external login.
        /// &lt;/summary>
        private async Task&lt;IActionResult> HandleUserCreationOrLinking(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider {Provider}", info.LoginProvider);
                return Redirect("/login?error=email_required");
            }

            var existingUser = await _userManager.FindByEmailAsync(email);
            return existingUser != null
                ? await LinkExistingUser(existingUser, info, returnUrl)
                : await CreateNewUserAsync(info, returnUrl);
        }

        /// &lt;summary>
        /// Links external login information to an existing user.
        /// &lt;/summary>
        private async Task&lt;IActionResult> LinkExistingUser(ApplicationUser user, ExternalLoginInfo info, string returnUrl)
        {
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (addLoginResult.Succeeded)
            {
                _logger.LogInformation("Successfully linked {Provider} login to existing user {Email}", info.LoginProvider, user.Email);
                await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }

            _logger.LogError("Failed to link external login to existing user: {Errors}",
                string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));
            return Redirect("/login?error=link_failed");
        }

        /// &lt;summary>
        /// Creates a new user based on external login information.
        /// &lt;/summary>
        private async Task&lt;IActionResult> CreateNewUserAsync(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider");
                return BadRequest(new { error = "Email information was not provided." });
            }

            var userName = email.Split('@')[0];
            var user = new ApplicationUser
            {
                UserName = email,
                Email = email,
                NickName = userName,
                EmailConfirmed = true // Email is already confirmed via external provider
            };

            var createResult = await _userManager.CreateAsync(user);
            if (!createResult.Succeeded)
            {
                _logger.LogError("User creation failed: {Errors}",
                    string.Join(", ", createResult.Errors.Select(e => e.Description)));

                // Attempt to create an alternative user name on a naming conflict
                if (createResult.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    user.UserName = $"{userName}_{Guid.NewGuid().ToString("N").Substring(0, 4)}";
                    createResult = await _userManager.CreateAsync(user);

                    if (!createResult.Succeeded)
                    {
                        return BadRequest(new
                        {
                            error = "Account creation failed",
                            details = createResult.Errors.Select(e => e.Description)
                        });
                    }
                }
                else
                {
                    return BadRequest(new
                    {
                        error = "Account creation failed",
                        details = createResult.Errors.Select(e => e.Description)
                    });
                }
            }

            // Link external login information
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (!addLoginResult.Succeeded)
            {
                _logger.LogError("Failed to add login information: {Errors}",
                    string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));

                // Rollback: Delete the created user
                await _userManager.DeleteAsync(user);

                return BadRequest(new
                {
                    error = "Failed to link external login information",
                    details = addLoginResult.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("New user created and logged in successfully: {Email}", email);
            await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);

            return LocalRedirect(returnUrl);
        }

        private ApplicationUser CreateUser()
        {
            try
            {
                return Activator.CreateInstance&lt;ApplicationUser>();
            }
            catch
            {
                throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
                    $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
            }
        }

        private IUserEmailStore&lt;ApplicationUser> GetEmailStore()
        {
            if (!_userManager.SupportsUserEmail)
            {
                throw new NotSupportedException("The default UI requires a user store with email support.");
            }
            return (IUserEmailStore&lt;ApplicationUser>)_userStore;
        }

        #endregion

        #region Profile Management

        /// &lt;summary>
        /// Handles user profile updates.
        /// &lt;/summary>
        /// &lt;param name="model">Update request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPut("updateProfile")]
        public async Task&lt;IActionResult> UpdateProfile([FromBody] UpdateUserProfileDTO model)
        {
            var user = await _userManager.FindByIdAsync(model.Email);
            if (user == null)
            {
                return NotFound("User not found");
            }

            user.NickName = model.Nickname;
            user.PhoneNumber = model.PhoneNumber;

            var result = await _userManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("Profile updated successfully");
        }

        /// &lt;summary>
        /// Handles user deletion.
        /// &lt;/summary>
        /// &lt;param name="userId">The ID of the user to delete&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpDelete("deleteUser/{userId}")]
        public async Task&lt;IActionResult> DeleteUser(string userId)
        {
            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                return NotFound("User not found");
            }

            var result = await _userManager.DeleteAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("User deleted successfully");
        }

        #endregion
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private void LoginWithGoogle()
{
    try
    {
        var returnUrl = "/";
        var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

        // Redirect in the current window instead of a popup
        // This is a placeholder for a client-side navigation method, e.g., in Blazor
        // Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
    }
    catch (Exception ex)
    {
        // Error handling
        Console.WriteLine($"Google login error: {ex.Message}");
    }
}
</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Ubuntu export 명령어 (.NET)</title>
		<link>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/</link>
					<comments>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 06:21:42 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[app-config]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[appsettings.development.json]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[appsettings.production.json]]></category>
		<category><![CDATA[appsettings.staging.json]]></category>
		<category><![CDATA[asp-net-core]]></category>
		<category><![CDATA[aspnetcore]]></category>
		<category><![CDATA[aspnetcore-environment]]></category>
		<category><![CDATA[aspnetcore-urls]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[bash_profile]]></category>
		<category><![CDATA[bashrc]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[child-process]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[command-line]]></category>
		<category><![CDATA[config]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[connection-string-env]]></category>
		<category><![CDATA[connection-strings]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[database-config]]></category>
		<category><![CDATA[database-connection]]></category>
		<category><![CDATA[db-connection]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[dotnet-config]]></category>
		<category><![CDATA[dotnet-core]]></category>
		<category><![CDATA[dotnet-environment]]></category>
		<category><![CDATA[dotnet-environment-variable]]></category>
		<category><![CDATA[dotnet-publish]]></category>
		<category><![CDATA[dotnet-run]]></category>
		<category><![CDATA[dotnet-settings]]></category>
		<category><![CDATA[env]]></category>
		<category><![CDATA[Environment]]></category>
		<category><![CDATA[environment-config]]></category>
		<category><![CDATA[environment-example]]></category>
		<category><![CDATA[environment-variable]]></category>
		<category><![CDATA[environment-variable-setup]]></category>
		<category><![CDATA[export]]></category>
		<category><![CDATA[export-command]]></category>
		<category><![CDATA[export-example]]></category>
		<category><![CDATA[export-variable]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-dotnet]]></category>
		<category><![CDATA[linux-export]]></category>
		<category><![CDATA[localhost]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[port-config]]></category>
		<category><![CDATA[printenv]]></category>
		<category><![CDATA[Process]]></category>
		<category><![CDATA[prod]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[production-server]]></category>
		<category><![CDATA[publish]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[secrets]]></category>
		<category><![CDATA[secure-environment-variable]]></category>
		<category><![CDATA[server-config]]></category>
		<category><![CDATA[server-environment]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[shell-variable]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Staging]]></category>
		<category><![CDATA[staging-server]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[system-environment]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[systemd-service]]></category>
		<category><![CDATA[Terminal]]></category>
		<category><![CDATA[test-environment]]></category>
		<category><![CDATA[ubuntu-dotnet]]></category>
		<category><![CDATA[ubuntu-dotnet-deploy]]></category>
		<category><![CDATA[url-binding]]></category>
		<category><![CDATA[개발]]></category>
		<category><![CDATA[개발서버]]></category>
		<category><![CDATA[개발환경]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[닷넷]]></category>
		<category><![CDATA[닷넷코어]]></category>
		<category><![CDATA[리눅스]]></category>
		<category><![CDATA[민감정보]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[배포서버]]></category>
		<category><![CDATA[배포환경]]></category>
		<category><![CDATA[서버환경]]></category>
		<category><![CDATA[설정파일]]></category>
		<category><![CDATA[쉘]]></category>
		<category><![CDATA[스테이징]]></category>
		<category><![CDATA[스테이징환경]]></category>
		<category><![CDATA[우분투]]></category>
		<category><![CDATA[운영환경]]></category>
		<category><![CDATA[터미널]]></category>
		<category><![CDATA[테스트서버]]></category>
		<category><![CDATA[테스트환경]]></category>
		<category><![CDATA[포트설정]]></category>
		<category><![CDATA[프로덕션]]></category>
		<category><![CDATA[프로덕션환경]]></category>
		<category><![CDATA[프로세스]]></category>
		<category><![CDATA[환경변수]]></category>
		<category><![CDATA[환경변수보안]]></category>
		<category><![CDATA[환경설정]]></category>
		<category><![CDATA[환경설정파일]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40241</guid>

					<description><![CDATA[<p>📄 Ubuntu export 명령어 1️⃣ 개요 export 명령어는 현재 셸 환경에 환경 변수를 등록하거나 수정하는 데 사용 등록된 변수는 현재 셸과 그 자식 프로세스에서 사용할 수 있음 2️⃣ 기본 문법 3️⃣ 특징 4️⃣ 확인 방법 💡 .NET에서의 사용 예시 1️⃣ ASP.NET Core에서 환경 지정 프로젝트 유형 권장 환경 변수 이유 ASP.NET Core 웹앱 ASPNETCORE_ENVIRONMENT 웹 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-b32ec816      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#ubuntu-export-명령어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-개요" class="uagb-toc-link__trigger">1&#x20e3; 개요</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-기본-문법" class="uagb-toc-link__trigger">2&#x20e3; 기본 문법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-특징" class="uagb-toc-link__trigger">3&#x20e3; 특징</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-확인-방법" class="uagb-toc-link__trigger">4&#x20e3; 확인 방법</a></li></ul></li><li class="uagb-toc__list"><a href="#net에서의-사용-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-aspnet-core에서-환경-지정" class="uagb-toc-link__trigger">1&#x20e3; ASP.NET Core에서 환경 지정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-net에서-특정-포트로-실행" class="uagb-toc-link__trigger">2&#x20e3; .NET에서 특정 포트로 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-영구-설정" class="uagb-toc-link__trigger">3&#x20e3; 영구 설정</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 개요</h3>



<p><code>export</code> 명령어는 <strong>현재 셸 환경에 환경 변수를 등록하거나 수정</strong>하는 데 사용</p>



<p>등록된 변수는 <strong>현재 셸과 그 자식 프로세스</strong>에서 사용할 수 있음</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 기본 문법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export 변수명=값

또는

export 변수명</pre>



<ul class="wp-block-list">
<li><code>변수명=값</code> : 새 환경 변수를 만들거나 기존 변수 값을 변경</li>



<li><code>변수명</code> : 이미 선언된 변수를 환경 변수로 승격</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 특징</h3>



<ul class="wp-block-list">
<li>환경 변수는 <strong>프로세스 간 전달</strong> 가능 (부모 → 자식).</li>



<li><code>export</code>로 설정한 변수는 <strong>현재 세션</strong>에서만 유효.</li>



<li>영구적으로 유지하려면 <code>~/.bashrc</code> 또는 <code>~/.bash_profile</code>에 추가해야 함.</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 확인 방법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">echo $변수명       # 변수 값 출력
printenv 변수명    # 환경 변수 확인
env                # 전체 환경 변수 목록
</pre>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; ASP.NET Core에서 환경 지정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export ASPNETCORE_ENVIRONMENT=Development
export ASPNETCORE_ENVIRONMENT=Staging
export ASPNETCORE_ENVIRONMENT=Production

export DOTNET_ENVIRONMENT=Development
export DOTNET_ENVIRONMENT=Staging // 실제 운영 환경(Production)과 거의 동일하지만, 사용자에게는 서비스되지 않는 사전 검증용 환경
export DOTNET_ENVIRONMENT=Production

dotnet run</pre>



<ul class="wp-block-list">
<li>예를 들어 <code>Development</code>로 설정하면, 개발 모드 환경 설정(<code>appsettings.Development.json</code>)을 자동으로 사용</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>프로젝트 유형</th><th>권장 환경 변수</th><th>이유</th></tr></thead><tbody><tr><td>ASP.NET Core 웹앱</td><td>ASPNETCORE_ENVIRONMENT</td><td>웹 호스팅에 최적화된 공식 표준이며 <code>DOTNET_ENVIRONMENT</code>보다 <strong>우선순위가 높</strong></td></tr><tr><td>Worker Service</td><td>DOTNET_ENVIRONMENT</td><td>웹과 관련 없는 일반적인 .NET 호스팅 환경을 위한 표준 변수</td></tr><tr><td>콘솔 앱</td><td>DOTNET_ENVIRONMENT</td><td>범용적 사용</td></tr><tr><td>Blazor</td><td>ASPNETCORE_ENVIRONMENT</td><td>ASP.NET Core 호스팅 모델을 기반으로 하므로 웹 앱과 동일한 기준</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; .NET에서 특정 포트로 실행</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 5005번 포트로 애플리케이션 실행
export ASPNETCORE_URLS="http://localhost:5005"

dotnet run</pre>



<ul class="wp-block-list">
<li><code>ASPNETCORE_URLS</code> 환경 변수를 이용해 실행 시 포트를 변경할 수 있음</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 영구 설정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 1. .bashrc 파일에 환경 변수 설정 명령어를 추가
echo 'export DOTNET_ENVIRONMENT=Production' >> ~/.bashrc

# 2. 수정된 .bashrc 설정을 현재 터미널 세션에 바로 적용
source ~/.bashrc

# 3. 설정 확인
echo $DOTNET_ENVIRONMENT</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core IdentityOptions 기본 예제</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 06 Aug 2025 09:44:34 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[2단계 인증]]></category>
		<category><![CDATA[Admin]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Audit]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[BCrypt]]></category>
		<category><![CDATA[Bearer Token]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[EF Core]]></category>
		<category><![CDATA[Entity Framework]]></category>
		<category><![CDATA[GDPR]]></category>
		<category><![CDATA[Guest]]></category>
		<category><![CDATA[Identity]]></category>
		<category><![CDATA[IdentityOptions]]></category>
		<category><![CDATA[ILogger]]></category>
		<category><![CDATA[IP 차단]]></category>
		<category><![CDATA[IPasswordValidator]]></category>
		<category><![CDATA[IUserValidator]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Lockout]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[password]]></category>
		<category><![CDATA[PasswordOptions]]></category>
		<category><![CDATA[Policy]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[Recovery Code]]></category>
		<category><![CDATA[Role]]></category>
		<category><![CDATA[Salt]]></category>
		<category><![CDATA[Seed Data]]></category>
		<category><![CDATA[SignIn]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[Stores]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[User]]></category>
		<category><![CDATA[감사]]></category>
		<category><![CDATA[개인정보 보호]]></category>
		<category><![CDATA[개인정보처리방침]]></category>
		<category><![CDATA[계정 보안]]></category>
		<category><![CDATA[계정 잠금]]></category>
		<category><![CDATA[계정 활성화]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[관리자]]></category>
		<category><![CDATA[권한 부여]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[다단계 인증]]></category>
		<category><![CDATA[데이터 보호]]></category>
		<category><![CDATA[데이터베이스]]></category>
		<category><![CDATA[레이트 리미팅]]></category>
		<category><![CDATA[로그인]]></category>
		<category><![CDATA[로깅]]></category>
		<category><![CDATA[마이그레이션]]></category>
		<category><![CDATA[모니터링]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[보안]]></category>
		<category><![CDATA[보안 이벤트]]></category>
		<category><![CDATA[보안 질문]]></category>
		<category><![CDATA[보안 토큰]]></category>
		<category><![CDATA[복구 코드]]></category>
		<category><![CDATA[브루트 포스]]></category>
		<category><![CDATA[비밀번호 재설정]]></category>
		<category><![CDATA[사용자 검증]]></category>
		<category><![CDATA[사용자 관리]]></category>
		<category><![CDATA[사용자 역할]]></category>
		<category><![CDATA[사용자 정보]]></category>
		<category><![CDATA[세션 관리]]></category>
		<category><![CDATA[소셜 로그인]]></category>
		<category><![CDATA[실패 시도]]></category>
		<category><![CDATA[암호화]]></category>
		<category><![CDATA[역할 기반 접근]]></category>
		<category><![CDATA[외부 로그인]]></category>
		<category><![CDATA[의존성 주입]]></category>
		<category><![CDATA[이메일 인증]]></category>
		<category><![CDATA[이메일 확인]]></category>
		<category><![CDATA[익명 사용자]]></category>
		<category><![CDATA[인증]]></category>
		<category><![CDATA[인증 토큰]]></category>
		<category><![CDATA[일반 사용자]]></category>
		<category><![CDATA[전화번호 인증]]></category>
		<category><![CDATA[정책 기반 인증]]></category>
		<category><![CDATA[초기 데이터]]></category>
		<category><![CDATA[커스텀 검증]]></category>
		<category><![CDATA[쿠키 인증]]></category>
		<category><![CDATA[토큰]]></category>
		<category><![CDATA[패스워드 복잡성]]></category>
		<category><![CDATA[패스워드 정책]]></category>
		<category><![CDATA[패스워드 찾기]]></category>
		<category><![CDATA[프로필 관리]]></category>
		<category><![CDATA[해싱]]></category>
		<category><![CDATA[환경 설정]]></category>
		<category><![CDATA[회원가입]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40232</guid>

					<description><![CDATA[<p>✨ ASP.NET Core IdentityOptions 개요 https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66 ASP.NET Core의 IdentityOptions는 시스템의 설정을 한 곳에 몰아서 관리하는 클래스입니다. (중앙 집중식 구성) 이를 통해 패스워드 정책, 사용자 설정, 로그인 정책, 잠금 정책 등을 세밀하게 제어할 수 있습니다. 🔥 주요 구성 요소 1️⃣ Password 옵션 (PasswordOptions) 패스워드 복잡성 요구사항을 설정합니다. 2️⃣ Lockout 옵션 (LockoutOptions) 계정 잠금 정책을 설정합니다. 3️⃣ [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/">ASP.NET Core IdentityOptions 기본 예제</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-346b5c77      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-identityoptions-개요" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core IdentityOptions 개요</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#주요-구성-요소" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주요 구성 요소</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-password-옵션-passwordoptions" class="uagb-toc-link__trigger">1&#x20e3; Password 옵션 (PasswordOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-lockout-옵션-lockoutoptions" class="uagb-toc-link__trigger">2&#x20e3; Lockout 옵션 (LockoutOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-signin-옵션-signinoptions" class="uagb-toc-link__trigger">3&#x20e3; SignIn 옵션 (SignInOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-user-옵션-useroptions" class="uagb-toc-link__trigger">4&#x20e3; User 옵션 (UserOptions)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-stores-옵션-storeoptions" class="uagb-toc-link__trigger">5&#x20e3; Stores 옵션 (StoreOptions)</a></li></ul><li class="uagb-toc__list"><a href="#실제-활용-시나리오" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f431.png" alt="🐱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 활용 시나리오</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-다단계-인증-설정" class="uagb-toc-link__trigger">1&#x20e3; 다단계 인증 설정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-소셜-로그인과-연계" class="uagb-toc-link__trigger">2&#x20e3; 소셜 로그인과 연계</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-커스텀-검증-규칙-적용" class="uagb-toc-link__trigger">3&#x20e3; 커스텀 검증 규칙 적용</a></li></ul><li class="uagb-toc__list"><a href="#참고-사항" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2747.png" alt="❇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 참고 사항</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-모니터링-및-로깅" class="uagb-toc-link__trigger">1&#x20e3; 모니터링 및 로깅</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-환경별-설정-관리" class="uagb-toc-link__trigger">2&#x20e3; 환경별 설정 관리</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h1 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core IdentityOptions 개요</h1>



<p><a href="https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66" target="_blank" rel="noreferrer noopener">https://github.com/dotnet/aspnetcore/blob/3f1acb59718cadf111a0a796681e3d3509bb3381/src/Identity/Extensions.Core/src/IdentityOptions.cs#L17C65-L65C66</a></p>



<p>ASP.NET Core의 IdentityOptions는 시스템의 설정을 한 곳에 몰아서 관리하는 클래스입니다. (중앙 집중식 구성)</p>



<p>이를 통해 <strong>패스워드 정책, 사용자 설정, 로그인 정책, 잠금 정책 등</strong>을 세밀하게 제어할 수 있습니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주요 구성 요소</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="872" height="287" src="https://lycos7560.com/wp-content/uploads/2025/08/image-12.png" alt="" class="wp-image-40235" srcset="https://lycos7560.com/wp-content/uploads/2025/08/image-12.png 872w, https://lycos7560.com/wp-content/uploads/2025/08/image-12-300x99.png 300w, https://lycos7560.com/wp-content/uploads/2025/08/image-12-768x253.png 768w" sizes="(max-width: 872px) 100vw, 872px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/ko-kr/dotnet/api/microsoft.aspnetcore.identity.identityoptions?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; Password 옵션 (PasswordOptions)</h3>



<p>패스워드 복잡성 요구사항을 설정합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs 또는 Startup.cs

// 개발 환경용 - 간단한 패스워드
public void ConfigureDevelopmentPassword(IdentityOptions options)
{
    options.Password.RequireDigit = false;
    options.Password.RequireLowercase = false;
    options.Password.RequireUppercase = false;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequiredLength = 4;
}

// 프로덕션 환경용 - 강력한 패스워드
public void ConfigureProductionPassword(IdentityOptions options)
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 12;
    options.Password.RequiredUniqueChars = 4;
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; Lockout 옵션 (LockoutOptions)</h3>



<p>계정 잠금 정책을 설정합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public void ConfigureProgressiveLockout(IdentityOptions options)
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);   // 1분부터 시작
    options.Lockout.MaxFailedAccessAttempts = 3;                        // 3회 실패시 잠금
    options.Lockout.AllowedForNewUsers = true;
}

// 커스텀 잠금 시간 증가 로직 (서비스에서 구현)
public class CustomLockoutService
{
    public TimeSpan CalculateLockoutDuration(int failedAttempts)
    {
        return failedAttempts switch
        {
            &lt;= 3 => TimeSpan.FromMinutes(5),
            &lt;= 6 => TimeSpan.FromMinutes(15),
            &lt;= 10 => TimeSpan.FromHours(1),
            _ => TimeSpan.FromHours(24)
        };
    }
}

public void ConfigureProgressiveLockout(IdentityOptions options)
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);   // 1분부터 시작
    options.Lockout.MaxFailedAccessAttempts = 3;                        // 3회 실패시 잠금
    options.Lockout.AllowedForNewUsers = true;
}

// 커스텀 잠금 시간 증가 로직 (서비스에서 구현)
public class CustomLockoutService
{
    public TimeSpan CalculateLockoutDuration(int failedAttempts)
    {
        return failedAttempts switch
        {
            &lt;= 3 => TimeSpan.FromMinutes(5),
            &lt;= 6 => TimeSpan.FromMinutes(15),
            &lt;= 10 => TimeSpan.FromHours(1),
            _ => TimeSpan.FromHours(24)
        };
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; SignIn 옵션 (SignInOptions)</h3>



<p>로그인 관련 설정을 구성합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 이메일 인증 필수 설정
public void ConfigureEmailConfirmation(IdentityOptions options)
{
    options.SignIn.RequireConfirmedEmail = true;
}

// 회원가입 컨트롤러
[HttpPost]
public async Task&lt;IActionResult> Register(RegisterViewModel model)
{
    var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
    var result = await _userManager.CreateAsync(user, model.Password);
    
    if (result.Succeeded)
    {
        // 이메일 인증 토큰 생성
        var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var confirmationLink = Url.Action("ConfirmEmail", "Account", 
            new { userId = user.Id, token = token }, Request.Scheme);
        
        // 이메일 발송
        await _emailSender.SendEmailAsync(model.Email, "이메일 인증", 
            $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}");
        
        return View("EmailConfirmationSent");
    }
    
    return View(model);
}

// 이메일 인증 필수 설정
public void ConfigureEmailConfirmation(IdentityOptions options)
{
    options.SignIn.RequireConfirmedEmail = true;
}

// 회원가입 컨트롤러
[HttpPost]
public async Task&lt;IActionResult> Register(RegisterViewModel model)
{
    var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
    var result = await _userManager.CreateAsync(user, model.Password);
    
    if (result.Succeeded)
    {
        // 이메일 인증 토큰 생성
        var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
        var confirmationLink = Url.Action("ConfirmEmail", "Account", 
            new { userId = user.Id, token = token }, Request.Scheme);
        
        // 이메일 발송
        await _emailSender.SendEmailAsync(model.Email, "이메일 인증", 
            $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}");
        
        return View("EmailConfirmationSent");
    }
    
    return View(model);
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; User 옵션 (UserOptions)</h3>



<p>사용자 계정 관련 설정을 구성합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">builder.Services.Configure&lt;IdentityOptions>(options =>
{
    // 사용자 설정
    options.User.AllowedUserNameCharacters = 
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = true;  // 중복 이메일 방지
});


사용자명 정책 예제:
public void ConfigureUserPolicies(IdentityOptions options)
{
    // 한국어 사용자명 허용
    options.User.AllowedUserNameCharacters = 
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+가-힣";
    
    // 이메일을 고유하게 설정
    options.User.RequireUniqueEmail = true;
}

// 커스텀 사용자명 검증
public class CustomUserValidator&lt;TUser> : IUserValidator&lt;TUser> 
    where TUser : class
{
    public Task&lt;IdentityResult> ValidateAsync(UserManager&lt;TUser> manager, TUser user)
    {
        var userName = manager.GetUserNameAsync(user).Result;
        
        // 사용자명이 숫자로만 구성되면 안됨
        if (userName.All(char.IsDigit))
        {
            return Task.FromResult(
                IdentityResult.Failed(new IdentityError
                {
                    Code = "NumericUserName",
                    Description = "사용자명은 숫자로만 구성될 수 없습니다."
                }));
        }
        
        return Task.FromResult(IdentityResult.Success);
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5&#x20e3; Stores 옵션 (StoreOptions)</h3>



<p>사용자 계정 관련 설정을 구성합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">builder.Services.Configure&lt;IdentityOptions>(options =>
{
    // 저장소 설정
    options.Stores.MaxLengthForKeys = 128;              // 키 최대 길이
    options.Stores.ProtectPersonalData = false;         // 개인정보 암호화 여부
});</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f431.png" alt="🐱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 활용 시나리오</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 다단계 인증 설정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public void ConfigureTwoFactorAuthentication(IdentityOptions options)
{
    options.SignIn.RequireConfirmedPhoneNumber = true;  // 전화번호 인증 필수
    options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
}

// 2FA 활성화 코드
[HttpPost]
public async Task&lt;IActionResult> EnableTwoFactorAuthentication()
{
    var user = await _userManager.GetUserAsync(User);
    await _userManager.SetTwoFactorEnabledAsync(user, true);
    
    return RedirectToAction("ShowRecoveryCodes");
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 소셜 로그인과 연계</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public void ConfigureExternalLogins(IdentityOptions options)
{
    // 외부 로그인 시에는 이메일 인증 건너뛰기
    options.SignIn.RequireConfirmedEmail = false;
    
    // 하지만 고유 이메일은 필수
    options.User.RequireUniqueEmail = true;
}

// 외부 로그인 처리
[HttpPost]
public IActionResult ExternalLogin(string provider)
{
    var redirectUrl = Url.Action("ExternalLoginCallback");
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
    return Challenge(properties, provider);
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 커스텀 검증 규칙 적용</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 커스텀 패스워드 검증기
public class CustomPasswordValidator : IPasswordValidator&lt;ApplicationUser>
{
    public Task&lt;IdentityResult> ValidateAsync(UserManager&lt;ApplicationUser> manager, 
        ApplicationUser user, string password)
    {
        // 사용자명과 패스워드가 너무 유사하면 안됨
        if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase))
        {
            return Task.FromResult(IdentityResult.Failed(new IdentityError
            {
                Code = "PasswordContainsUserName",
                Description = "패스워드에 사용자명이 포함될 수 없습니다."
            }));
        }
        
        return Task.FromResult(IdentityResult.Success);
    }
}

// 서비스 등록
builder.Services.AddTransient&lt;IPasswordValidator&lt;ApplicationUser>, CustomPasswordValidator>();</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2747.png" alt="❇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 참고 사항</h2>



<div style="height:15px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 모니터링 및 로깅</h3>



<p>보안 이벤트 로깅</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class SecurityEventLogger
{
    private readonly ILogger&lt;SecurityEventLogger> _logger;
    
    public SecurityEventLogger(ILogger&lt;SecurityEventLogger> logger)
    {
        _logger = logger;
    }
    
    public void LogFailedLogin(string userName, string ipAddress)
    {
        _logger.LogWarning("Failed login attempt for user {UserName} from IP {IpAddress}", 
            userName, ipAddress);
    }
    
    public void LogAccountLocked(string userName)
    {
        _logger.LogWarning("Account locked for user {UserName}", userName);
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 환경별 설정 관리</h3>



<p>appsettings.json을 활용한 설정</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "Identity": {
    "Password": {
      "RequiredLength": 8,
      "RequireDigit": true,
      "RequireUppercase": true,
      "RequireNonAlphanumeric": false
    },
    "Lockout": {
      "MaxFailedAccessAttempts": 5,
      "DefaultLockoutTimeSpanMinutes": 15
    },
    "SignIn": {
      "RequireConfirmedEmail": true
    }
  }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 설정 바인딩
public class IdentityConfig
{
    public PasswordConfig Password { get; set; }
    public LockoutConfig Lockout { get; set; }
    public SignInConfig SignIn { get; set; }
}

// Program.cs에서 적용
var identityConfig = builder.Configuration.GetSection("Identity").Get&lt;IdentityConfig>();

builder.Services.Configure&lt;IdentityOptions>(options =>
{
    options.Password.RequiredLength = identityConfig.Password.RequiredLength;
    options.Password.RequireDigit = identityConfig.Password.RequireDigit;
    // ... 기타 설정
});</pre>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/">ASP.NET Core IdentityOptions 기본 예제</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-identityoptions-%ec%a0%95%eb%a6%ac/40232/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>HTML iframe 정리</title>
		<link>https://lycos7560.com/etc/html-iframe-%ec%a0%95%eb%a6%ac/40225/</link>
					<comments>https://lycos7560.com/etc/html-iframe-%ec%a0%95%eb%a6%ac/40225/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 04 Aug 2025 12:42:26 +0000</pubDate>
				<category><![CDATA[Blazor]]></category>
		<category><![CDATA[기타]]></category>
		<category><![CDATA[a11y]]></category>
		<category><![CDATA[Accessibility]]></category>
		<category><![CDATA[allow]]></category>
		<category><![CDATA[allow-forms]]></category>
		<category><![CDATA[allow-popups]]></category>
		<category><![CDATA[allow-same-origin]]></category>
		<category><![CDATA[allow-scripts]]></category>
		<category><![CDATA[allowfullscreen]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Aspect Ratio]]></category>
		<category><![CDATA[autoplay]]></category>
		<category><![CDATA[Browser]]></category>
		<category><![CDATA[camera]]></category>
		<category><![CDATA[Clickjacking]]></category>
		<category><![CDATA[compatibility]]></category>
		<category><![CDATA[Content Security Policy]]></category>
		<category><![CDATA[CORS]]></category>
		<category><![CDATA[cross-origin]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[DevTools]]></category>
		<category><![CDATA[DOM]]></category>
		<category><![CDATA[embed]]></category>
		<category><![CDATA[embed 태그]]></category>
		<category><![CDATA[Frame]]></category>
		<category><![CDATA[Frontend]]></category>
		<category><![CDATA[geolocation]]></category>
		<category><![CDATA[Google Maps]]></category>
		<category><![CDATA[height]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[HTML5]]></category>
		<category><![CDATA[HTML기초]]></category>
		<category><![CDATA[HTML태그]]></category>
		<category><![CDATA[iframe]]></category>
		<category><![CDATA[iframe 통신]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[lazy loading]]></category>
		<category><![CDATA[loading]]></category>
		<category><![CDATA[Map Embed]]></category>
		<category><![CDATA[microphone]]></category>
		<category><![CDATA[object 태그]]></category>
		<category><![CDATA[Optimization]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[PDF뷰어]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Permissions Policy]]></category>
		<category><![CDATA[picture-in-picture]]></category>
		<category><![CDATA[postMessage]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[referrerpolicy]]></category>
		<category><![CDATA[Responsive iframe]]></category>
		<category><![CDATA[Responsive Web]]></category>
		<category><![CDATA[Same-Origin Policy]]></category>
		<category><![CDATA[sandbox]]></category>
		<category><![CDATA[Screen Reader]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SPA]]></category>
		<category><![CDATA[src]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Third-Party Content]]></category>
		<category><![CDATA[title]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[Vue.js]]></category>
		<category><![CDATA[WAI-ARIA]]></category>
		<category><![CDATA[Web Components]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Performance]]></category>
		<category><![CDATA[Web Security]]></category>
		<category><![CDATA[Web Standards]]></category>
		<category><![CDATA[width]]></category>
		<category><![CDATA[X-Frame-Options]]></category>
		<category><![CDATA[YouTube]]></category>
		<category><![CDATA[YouTube Embed]]></category>
		<category><![CDATA[개발자도구]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[구글맵임베드]]></category>
		<category><![CDATA[구글지도]]></category>
		<category><![CDATA[기능정책]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[동일출처정책]]></category>
		<category><![CDATA[디버깅]]></category>
		<category><![CDATA[리액트]]></category>
		<category><![CDATA[문제해결]]></category>
		<category><![CDATA[반응형iframe]]></category>
		<category><![CDATA[반응형웹]]></category>
		<category><![CDATA[보안]]></category>
		<category><![CDATA[뷰]]></category>
		<category><![CDATA[브라우저]]></category>
		<category><![CDATA[블레이저]]></category>
		<category><![CDATA[성능]]></category>
		<category><![CDATA[스크린리더]]></category>
		<category><![CDATA[싱글페이지애플리케이션]]></category>
		<category><![CDATA[외부콘텐츠]]></category>
		<category><![CDATA[웹개발]]></category>
		<category><![CDATA[웹메시징]]></category>
		<category><![CDATA[웹보안]]></category>
		<category><![CDATA[웹성능]]></category>
		<category><![CDATA[웹접근성]]></category>
		<category><![CDATA[웹컴포넌트]]></category>
		<category><![CDATA[웹퍼블리셔]]></category>
		<category><![CDATA[웹페이지]]></category>
		<category><![CDATA[웹표준]]></category>
		<category><![CDATA[유튜브]]></category>
		<category><![CDATA[유튜브임베드]]></category>
		<category><![CDATA[이벤트리스너]]></category>
		<category><![CDATA[인라인요소]]></category>
		<category><![CDATA[인라인프레임]]></category>
		<category><![CDATA[임베드]]></category>
		<category><![CDATA[자바스크립트]]></category>
		<category><![CDATA[접근성]]></category>
		<category><![CDATA[종횡비]]></category>
		<category><![CDATA[지연로딩]]></category>
		<category><![CDATA[최적화]]></category>
		<category><![CDATA[코딩]]></category>
		<category><![CDATA[클릭재킹]]></category>
		<category><![CDATA[키보드네비게이션]]></category>
		<category><![CDATA[퍼블리싱]]></category>
		<category><![CDATA[프레임]]></category>
		<category><![CDATA[프레임워크]]></category>
		<category><![CDATA[프로그래밍]]></category>
		<category><![CDATA[프론트엔드]]></category>
		<category><![CDATA[호환성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40225</guid>

					<description><![CDATA[<p>HTML iframe? &#60;iframe> (Inline Frame) 태그는 현재 HTML 문서 안에 다른 웹 페이지나 미디어 콘텐츠를 임베드(embed)할 때 사용하는 HTML 요소입니다. 외부 콘텐츠를 현재 페이지에 &#8220;창문처럼&#8221; 삽입하는 기능을 제공하며, 웹 개발에서 매우 유용한 도구입니다. 1. 기본 구조와 문법 기본 문법 최소 권장 구조 2. 핵심 속성 속성 설명 예시 필수여부 src 임베드할 콘텐츠의 URL src="https://www.youtube.com/embed/동영상ID" 필수 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/etc/html-iframe-%ec%a0%95%eb%a6%ac/40225/">HTML iframe 정리</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-d4c23dad      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#html-iframe" class="uagb-toc-link__trigger">HTML iframe?</a><li class="uagb-toc__list"><a href="#1-기본-구조와-문법" class="uagb-toc-link__trigger">1. 기본 구조와 문법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#기본-문법" class="uagb-toc-link__trigger">기본 문법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#최소-권장-구조" class="uagb-toc-link__trigger">최소 권장 구조</a></li></ul></li><li class="uagb-toc__list"><a href="#2-핵심-속성" class="uagb-toc-link__trigger">2. 핵심 속성</a><li class="uagb-toc__list"><a href="#3-주요-사용-사례" class="uagb-toc-link__trigger">3. 주요 사용 사례</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-youtube-동영상-임베드" class="uagb-toc-link__trigger">1) YouTube 동영상 임베드</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-google-maps-임베드" class="uagb-toc-link__trigger">2) Google Maps 임베드</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-pdf-문서-표시" class="uagb-toc-link__trigger">3) PDF 문서 표시</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-인라인-html-콘텐츠-srcdoc-사용" class="uagb-toc-link__trigger">4) 인라인 HTML 콘텐츠 (srcdoc 사용)</a></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#4-보안-및-권한-제어-속성-sandbox-allow" class="uagb-toc-link__trigger">4. 보안 및 권한 제어 속성 (sandbox &amp; allow)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#41-sandbox-속성-보안-제한-설정" class="uagb-toc-link__trigger">4.1 sandbox 속성 &#8211; 보안 제한 설정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#42-allow-속성-기능-권한-제어" class="uagb-toc-link__trigger">4.2 allow 속성 &#8211; 기능 권한 제어</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#43-사용-예시" class="uagb-toc-link__trigger">4.3 사용 예시</a></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#5-보안-및-성능-고려사항" class="uagb-toc-link__trigger">5. 보안 및 성능 고려사항</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#보안-고려사항" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 보안 고려사항</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#성능-최적화" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 성능 최적화</a></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#6-반응형-디자인" class="uagb-toc-link__trigger">6. 반응형 디자인</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#css를-이용한-반응형-iframe" class="uagb-toc-link__trigger">CSS를 이용한 반응형 iframe</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#다양한-비율-지원" class="uagb-toc-link__trigger">다양한 비율 지원</a></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#7-javascript와의-상호작용" class="uagb-toc-link__trigger">7. JavaScript와의 상호작용</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#postmessage-api-사용" class="uagb-toc-link__trigger">postMessage API 사용</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#iframe-로드-완료-감지" class="uagb-toc-link__trigger">iframe 로드 완료 감지</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#8-프레임워크별-사용-예시" class="uagb-toc-link__trigger">8. 프레임워크별 사용 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#react" class="uagb-toc-link__trigger">React</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#vuejs" class="uagb-toc-link__trigger">Vue.js</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#blazor" class="uagb-toc-link__trigger">Blazor</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#9-접근성-accessibility" class="uagb-toc-link__trigger">9. 접근성 (Accessibility)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#필수-접근성-고려사항" class="uagb-toc-link__trigger">필수 접근성 고려사항</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#키보드-네비게이션" class="uagb-toc-link__trigger">키보드 네비게이션</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#10-문제-해결-및-디버깅" class="uagb-toc-link__trigger">10. 문제 해결 및 디버깅</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#일반적인-문제들" class="uagb-toc-link__trigger">일반적인 문제들</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#디버깅-도구" class="uagb-toc-link__trigger">디버깅 도구</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#11-모범-사례-및-권장사항" class="uagb-toc-link__trigger">11. 모범 사례 및 권장사항</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#권장사항" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권장사항</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#피해야-할-것들" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 피해야 할 것들</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#12-대안-기술들" class="uagb-toc-link__trigger">12. 대안 기술들</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-object-태그" class="uagb-toc-link__trigger">1. object 태그</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-embed-태그" class="uagb-toc-link__trigger">2. embed 태그</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-web-components" class="uagb-toc-link__trigger">3. Web Components</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#결론" class="uagb-toc-link__trigger">결론</a></ul></ul></ul></ul></ul></ul></ul></ul></ul></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">HTML iframe?</h2>



<p><code>&lt;iframe></code> (Inline Frame) 태그는 <strong>현재 HTML 문서 안에 다른 웹 페이지나 미디어 콘텐츠를 임베드(embed)할 때 사용하는 HTML 요소</strong>입니다. </p>



<p>외부 콘텐츠를 현재 페이지에 &#8220;창문처럼&#8221; 삽입하는 기능을 제공하며, <strong>웹 개발에서 매우 유용한 도구</strong>입니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">1. 기본 구조와 문법</h2>



<h3 class="wp-block-heading">기본 문법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe 
    src="삽입할_콘텐츠_URL" 
    width="너비" 
    height="높이" 
    title="프레임 설명"
    allow="fullscreen">
    &lt;!-- 대체 콘텐츠 (iframe을 지원하지 않는 브라우저용) -->
    &lt;p>이 브라우저는 iframe을 지원하지 않습니다.&lt;/p>
&lt;/iframe>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">최소 권장 구조</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe 
    src="https://example.com" 
    title="Example Website"
    width="600" 
    height="400">
&lt;/iframe>
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">2. 핵심 속성</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>속성</th><th>설명</th><th>예시</th><th>필수여부</th></tr></thead><tbody><tr><td><code>src</code></td><td>임베드할 콘텐츠의 URL</td><td><code>src="https://www.youtube.com/embed/동영상ID"</code></td><td><strong>필수</strong></td></tr><tr><td><code>width</code>/<code>height</code></td><td>프레임 크기 (픽셀 또는 %)</td><td><code>width="600" height="400"</code></td><td>권장</td></tr><tr><td><code>title</code></td><td>접근성을 위한 프레임 설명</td><td><code>title="YouTube 동영상"</code></td><td><strong>권장</strong></td></tr><tr><td><code><s>frameborder</s></code></td><td><s>테두리 표시 여부 </s>(HTML5에선 CSS로 대체)</td><td><code><s>frameborder="0"</s></code></td><td><s>선택</s></td></tr><tr><td><code>allowfullscreen</code><br><code>allowfullscreen</code>은 오래된 방식이며, <br>최신 표준인 <strong>권한 정책(Permissions Policy)에서는</strong><br><strong><code>allow="fullscreen"</code>을 사용</strong>하는 것을 권장</td><td>전체 화면 모드 허용</td><td>불리언 속성</td><td>선택</td></tr><tr><td><code>sandbox</code></td><td>보안 제한 설정</td><td><code>sandbox="allow-scripts allow-same-origin"</code></td><td>선택</td></tr><tr><td><code>loading</code></td><td>로딩 방식 (lazy/eager)</td><td><code>loading="lazy"</code></td><td>선택</td></tr><tr><td><code>allow</code></td><td>기능 정책 설정</td><td><code>allow="camera; microphone"</code></td><td>선택</td></tr><tr><td><code>name</code></td><td>프레임 이름 (target으로 사용)</td><td><code>name="myFrame"</code></td><td>선택</td></tr><tr><td><code>srcdoc</code></td><td>인라인 HTML 콘텐츠</td><td><code>srcdoc="&lt;p&gt;Hello World&lt;/p&gt;"</code></td><td>선택</td></tr><tr><td><code>id</code></td><td>고유 식별자 (CSS, JavaScript에서 참조)</td><td><code>id="myIframe"</code></td><td>권장</td></tr></tbody></table></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">3. 주요 사용 사례</h2>



<h3 class="wp-block-heading">1) YouTube 동영상 임베드</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;div class="video-container">
    &lt;iframe src="https://www.youtube.com/embed/rdwz7QiG0lk?enablejsapi=1"
            id="ytPlayer"
            width="560"
            height="315"
            title="YouTube on the tube!"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; fullscreen; picture-in-picture; web-share">
    &lt;/iframe>
&lt;/div>

&lt;style>
    .video-container {
        position: relative;
        padding-bottom: 56.25%; /* 16:9 비율 */
        height: 0;
        overflow: hidden;
    }

    .video-container iframe {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
    }
&lt;/style></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2) Google Maps 임베드</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe
    src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3165.303576908283!2d127.024612!3d37.515582!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zMzfCsDMwJzU2LjEiTiAxMjfCsDAxJzI4LjYiRQ!5e0!3m2!1sen!2skr!4v1620000000000!5m2!1sen!2skr"
    width="600"
    height="450"
    title="Google Maps"
    style="border:0;"
    allow="fullscreen"
    loading="lazy"
    referrerpolicy="no-referrer-when-downgrade">
&lt;/iframe>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3) PDF 문서 표시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
&lt;iframe 
    src="/documents/sample.pdf" 
    width="100%" 
    height="600px"
    title="PDF 문서">
    &lt;p>PDF를 표시할 수 없습니다. &lt;a href="/documents/sample.pdf">다운로드&lt;/a>하세요.&lt;/p>
&lt;/iframe>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4) 인라인 HTML 콘텐츠 (srcdoc 사용)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe 
    srcdoc="&lt;html>&lt;body>&lt;h1>안녕하세요!&lt;/h1>&lt;p>이것은 인라인 HTML입니다.&lt;/p>&lt;/body>&lt;/html>"
    width="400" 
    height="200"
    title="인라인 HTML">
&lt;/iframe>
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">4. 보안 및 권한 제어 속성 (sandbox &amp; allow)</h2>



<h3 class="wp-block-heading">4.1 sandbox 속성 &#8211; 보안 제한 설정</h3>



<p><code>sandbox</code> 속성은 iframe 내 콘텐츠의 보안을 강화하기 위해 사용됩니다. </p>



<p><strong>기본적으로 모든 것을 차단</strong>하고 필요한 기능만 허용하는 방식입니다.</p>



<h4 class="wp-block-heading">sandbox 기본 사용법</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- 모든 제한 적용 (최고 보안) -->
&lt;iframe src="..." sandbox>&lt;/iframe>

&lt;!-- 특정 기능만 허용 -->
&lt;iframe src="..." sandbox="allow-scripts allow-same-origin allow-forms">&lt;/iframe></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">sandbox 값들</h4>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>sandbox 값</th><th>설명</th><th>사용 예시</th></tr></thead><tbody><tr><td><code>allow-forms</code></td><td>폼 제출 허용</td><td>로그인, 검색 폼이 있는 페이지</td></tr><tr><td><code>allow-scripts</code></td><td>JavaScript 실행 허용</td><td>인터랙티브 콘텐츠</td></tr><tr><td><code>allow-same-origin</code></td><td>동일 출처 정책 적용</td><td>API 호출, 쿠키 접근</td></tr><tr><td><code>allow-popups</code></td><td>팝업 창 열기 허용</td><td>새 창으로 링크 열기</td></tr><tr><td><code>allow-top-navigation</code></td><td>최상위 프레임 네비게이션 허용</td><td>부모 페이지 이동</td></tr><tr><td><code>allow-pointer-lock</code></td><td>포인터 잠금 API 허용</td><td>게임, 3D 앱</td></tr><tr><td><code>allow-fullscreen</code></td><td>전체 화면 모드 허용</td><td>동영상 플레이어</td></tr><tr><td><code>allow-downloads</code></td><td>파일 다운로드 허용</td><td>문서 다운로드</td></tr><tr><td><code>allow-modals</code></td><td>모달 창 허용</td><td>alert, confirm 등</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4.2 allow 속성 &#8211; 기능 권한 제어</h3>



<p><code>allow</code> 속성은 브라우저의 고급 기능에 대한 접근 권한을 제어합니다. </p>



<p><strong>Permissions Policy</strong>를 통해 특정 웹 API 사용을 허용하거나 차단합니다.</p>



<h4 class="wp-block-heading">allow 기본 사용법</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- 특정 브라우저 기능 허용 -->
&lt;iframe src="..." 
        allow="camera; microphone; geolocation">
&lt;/iframe>

&lt;!-- YouTube 동영상에 필요한 기능들 -->
&lt;iframe src="https://www.youtube.com/embed/..."
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share">
&lt;/iframe></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">allow 값들</h4>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>값</strong></th><th><strong>설명</strong></th><th><strong>사용 예시</strong></th></tr></thead><tbody><tr><td><code>camera</code></td><td>카메라 접근 허용</td><td>화상 통화, 사진 촬영</td></tr><tr><td><code>microphone</code></td><td>마이크 접근 허용</td><td>음성 녹음, 통음</td></tr><tr><td><code>geolocation</code></td><td>위치 정보 접근 허용</td><td>지도 서비스, 위치 기반 기능</td></tr><tr><td><code>autoplay</code></td><td>자동 재생 허용</td><td>동영상/음악 자동 재생</td></tr><tr><td><code>encrypted-media</code></td><td>DRM 보호 콘텐츠 재생 허용</td><td>Netflix, 유료 스트리밍 서비스</td></tr><tr><td><code>picture-in-picture</code></td><td>PIP(작은 창) 모드 허용</td><td>동영상 시청 중 다른 작업</td></tr><tr><td><code>accelerometer</code></td><td>가속도계(기기 움직임) 접근 허용</td><td>모바일 게임, VR 콘텐츠</td></tr><tr><td><code>gyroscope</code></td><td>자이로스코프(기기 회전) 접근 허용</td><td>360도 동영상, AR 앱</td></tr><tr><td><code>clipboard-write</code></td><td>클립보드 복사 기능 허용</td><td>텍스트/이미지 복사 버튼</td></tr><tr><td><code>web-share</code></td><td>운영체제 공유 기능 허용</td><td>SNS 공유, 링크 공유</td></tr><tr><td><code>fullscreen</code></td><td>전체 화면 모드 허용</td><td>동영상 전체 화면으로 보기</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4.3 사용 예시</h3>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">신뢰할 수 없는 콘텐츠 (높은 보안)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- 외부 광고, 사용자 생성 콘텐츠 -->
&lt;iframe src="https://untrusted-ads.com/banner" 
        sandbox="allow-scripts"
        width="300" height="250">
&lt;/iframe></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">부분적으로 신뢰하는 콘텐츠 (중간 보안)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- 서드파티 앱, 외부 도구 -->
&lt;iframe src="https://third-party-tool.com"
        sandbox="allow-scripts allow-same-origin allow-forms"
        allow="clipboard-write"
        width="600" height="400">
&lt;/iframe></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">신뢰할 수 있는 서비스 (기능 중심)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- YouTube, Google Maps 등 -->
&lt;iframe src="https://www.youtube.com/embed/rdwz7QiG0lk"
        allow="accelerometer; autoplay; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share">
&lt;/iframe></pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">둘 다 사용하는 경우</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- 보안과 기능 모두 제어 -->
&lt;iframe src="https://video-chat-app.com"
        sandbox="allow-scripts allow-same-origin allow-forms"
        allow="camera; microphone; autoplay"
        width="800" height="600">
&lt;/iframe></pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">5. 보안 및 성능 고려사항</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 보안 고려사항</h3>



<h4 class="wp-block-heading">X-Frame-Options 헤더</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">X-Frame-Options: DENY          # 모든 프레임에서 차단
X-Frame-Options: SAMEORIGIN   # 동일 출처에서만 허용
X-Frame-Options: ALLOW-FROM https://example.com  # 대부분 지원 중단되었으므로 CSP frame-ancestors를 사용

# "특정 도메인"은 해당 웹사이트를 iframe으로 임베드할 수 있는 허용된 출처(도메인)를 의미</pre>



<figure class="wp-block-image size-full"><img decoding="async" width="965" height="490" src="https://lycos7560.com/wp-content/uploads/2025/08/image-13.png" alt="" class="wp-image-40229" srcset="https://lycos7560.com/wp-content/uploads/2025/08/image-13.png 965w, https://lycos7560.com/wp-content/uploads/2025/08/image-13-300x152.png 300w, https://lycos7560.com/wp-content/uploads/2025/08/image-13-768x390.png 768w" sizes="(max-width: 965px) 100vw, 965px" /><figcaption class="wp-element-caption"><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options#browser_compatibility" target="_blank" rel="noreferrer noopener">https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options#browser_compatibility</a></figcaption></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">Content Security Policy (CSP)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;meta http-equiv="Content-Security-Policy" 
      content="frame-src https://youtube.com https://maps.google.com;">
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">안전한 iframe 사용법</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe 
    src="https://trusted-site.com"
    sandbox="allow-scripts allow-same-origin"
    referrerpolicy="strict-origin-when-cross-origin">
&lt;/iframe>
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 성능 최적화</h3>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">지연 로딩 (Lazy Loading)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe 
    src="https://example.com" 
    loading="lazy"
    width="600" 
    height="400">
&lt;/iframe>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">리소스 힌트 사용</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;!-- DNS 사전 해석 -->
&lt;link rel="dns-prefetch" href="https://www.youtube.com">

&lt;!-- 사전 연결 -->
&lt;link rel="preconnect" href="https://www.youtube.com">

&lt;!-- 리소스 사전 로드 -->
&lt;link rel="preload" href="https://example.com/critical-frame" as="document">
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">Intersection Observer를 이용한 동적 로딩</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const iframe = entry.target;
            iframe.src = iframe.dataset.src;
            observer.unobserve(iframe);
        }
    });
});

document.querySelectorAll('iframe[data-src]').forEach(iframe => {
    observer.observe(iframe);
});
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">6. 반응형 디자인</h2>



<h3 class="wp-block-heading">CSS를 이용한 반응형 iframe</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">.iframe-container {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; /* 16:9 비율 */
    height: 0;
    overflow: hidden;
}

.iframe-container iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;div class="iframe-container">
    &lt;iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" 
            title="YouTube 동영상"
            allow="fullscreen">
    &lt;/iframe>
&lt;/div>
</pre>



<h3 class="wp-block-heading">다양한 비율 지원</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">.aspect-16-9 { padding-bottom: 56.25%; }  /* 16:9 */
.aspect-4-3 { padding-bottom: 75%; }      /* 4:3 */
.aspect-1-1 { padding-bottom: 100%; }     /* 1:1 */
.aspect-21-9 { padding-bottom: 42.86%; }  /* 21:9 */
</pre>



<h2 class="wp-block-heading">7. JavaScript와의 상호작용</h2>



<h3 class="wp-block-heading">postMessage API 사용</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 부모 페이지에서 iframe으로 메시지 전송
const iframe = document.getElementById('myFrame');
iframe.contentWindow.postMessage('Hello from parent', '*');

// 메시지 수신 리스너
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://trusted-domain.com') return;
    console.log('받은 메시지:', event.data);
});
</pre>



<h3 class="wp-block-heading">iframe 로드 완료 감지</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">const iframe = document.getElementById('myFrame');
iframe.addEventListener('load', () => {
    console.log('iframe 로드 완료');
});
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">8. 프레임워크별 사용 예시</h2>



<h3 class="wp-block-heading">React</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">function YouTubeEmbed({ videoId }) {
    return (
        &lt;div className="iframe-container">
            &lt;iframe
                src={`https://www.youtube.com/embed/${videoId}`}
                title="YouTube video player"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
            />
        &lt;/div>
    );
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Vue.js</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;template>
    &lt;div class="iframe-container">
        &lt;iframe
            :src="`https://www.youtube.com/embed/${videoId}`"
            title="YouTube video player"
            allow="fullscreen"
        />
    &lt;/div>
&lt;/template>

&lt;script>
export default {
    props: ['videoId']
}
&lt;/script>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Blazor</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@page "/iframe-demo"

&lt;div class="iframe-wrapper">
    &lt;iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"
            width="560"
            height="315"
            title="YouTube 동영상"
            allow="fullscreen"
            class="youtube-frame">
    &lt;/iframe>
&lt;/div>

&lt;style>
    .youtube-frame {
        border: none;
        border-radius: 8px;
        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }
    
    .iframe-wrapper {
        display: flex;
        justify-content: center;
        margin: 20px 0;
    }
&lt;/style>
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">9. 접근성 (Accessibility)</h2>



<h3 class="wp-block-heading">필수 접근성 고려사항</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;iframe
    src="https://example.com"
    title="명확하고 설명적인 제목"
    aria-label="iframe 내용에 대한 추가 설명"
    tabindex="0">
    &lt;p>이 콘텐츠를 보려면 &lt;a href="https://example.com">여기를 클릭&lt;/a>하세요.&lt;/p>
&lt;/iframe>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">키보드 네비게이션</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="css" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">iframe:focus {
    outline: 2px solid #007cba;
    outline-offset: 2px;
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">10. 문제 해결 및 디버깅</h2>



<h3 class="wp-block-heading">일반적인 문제들</h3>



<h4 class="wp-block-heading">1. 콘텐츠가 보이지 않을 때</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 개발자 도구에서 확인할 사항들
console.log('iframe src:', iframe.src);
console.log('iframe 로드 상태:', iframe.readyState);

// 오류 핸들링
iframe.addEventListener('error', (e) => {
    console.error('iframe 로드 오류:', e);
});
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">2. X-Frame-Options 오류 해결</h4>



<ul class="wp-block-list">
<li>개발자 도구 → Network 탭에서 응답 헤더 확인</li>



<li>가능한 해결책: 프록시 서버 사용 또는 API를 통한 콘텐츠 가져오기</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">3. CORS 정책 위반</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 안전한 방법: 서버 사이드에서 콘텐츠 프록시
fetch('/api/proxy?url=' + encodeURIComponent(targetUrl))
    .then(response => response.text())
    .then(html => {
        iframe.srcdoc = html;
    });
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">디버깅 도구</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// iframe 상태 체크 함수
function checkIframeStatus(iframeId) {
    const iframe = document.getElementById(iframeId);
    console.log({
        src: iframe.src,
        contentDocument: iframe.contentDocument,
        contentWindow: iframe.contentWindow,
        readyState: iframe.readyState
    });
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">11. 모범 사례 및 권장사항</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권장사항</h3>



<ol class="wp-block-list">
<li><strong>항상 <code>title</code> 속성 제공</strong> &#8211; 접근성을 위해 필수</li>



<li><strong>적절한 크기 설정</strong> &#8211; 반응형 디자인 고려</li>



<li><strong>loading=&#8221;lazy&#8221; 사용</strong> &#8211; 성능 최적화</li>



<li><strong>sandbox 속성 활용</strong> &#8211; 보안 강화</li>



<li><strong>대체 콘텐츠 제공</strong> &#8211; 호환성 보장</li>



<li><strong>HTTPS 사용</strong> &#8211; 보안 및 호환성</li>
</ol>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 피해야 할 것들</h3>



<ol class="wp-block-list">
<li><strong>frameborder 속성</strong> &#8211; HTML5에서는 CSS 사용 권장</li>



<li><strong>과도한 중첩</strong> &#8211; 성능 저하 원인</li>



<li><strong>신뢰할 수 없는 소스</strong> &#8211; 보안 위험</li>



<li><strong>고정 크기만 사용</strong> &#8211; 반응형 웹에 부적합</li>



<li><strong>title 속성 누락</strong> &#8211; 접근성 문제</li>
</ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">12. 대안 기술들</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1. object 태그</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;object data="document.pdf" type="application/pdf" width="600" height="400">
    &lt;p>PDF를 표시할 수 없습니다. &lt;a href="document.pdf">다운로드&lt;/a>하세요.&lt;/p>
&lt;/object>
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. embed 태그</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;embed src="movie.mp4" type="video/mp4" width="600" height="400">
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. Web Components</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class CustomEmbed extends HTMLElement {
    connectedCallback() {
        this.innerHTML = `
            &lt;iframe src="${this.getAttribute('src')}" 
                    title="${this.getAttribute('title')}">
            &lt;/iframe>
        `;
    }
}
customElements.define('custom-embed', CustomEmbed);
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">결론</h2>



<p>iframe은 웹 개발에서 외부 콘텐츠를 삽입하는 강력한 도구입니다. </p>



<p>올바른 사용법과 보안 고려사항을 숙지하여 안전하고 효율적인 웹 애플리케이션을 개발할 수 있습니다. </p>



<p>특히 현대 웹 개발에서는 성능, 보안, 접근성을 모두 고려한 구현이 중요합니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/etc/html-iframe-%ec%a0%95%eb%a6%ac/40225/">HTML iframe 정리</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/etc/html-iframe-%ec%a0%95%eb%a6%ac/40225/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</title>
		<link>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/</link>
					<comments>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/#comments</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 28 Jul 2025 08:39:46 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[ActionResult]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[리플렉션]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40153</guid>

					<description><![CDATA[<p>The post <a href="https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/">리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;

// 1. ActionResult를 상속받는 모든 구체적인 클래스 찾기
var actionResultTypes = typeof(ActionResult).Assembly
    .GetTypes()
    .Where(t => t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; typeof(ActionResult).IsAssignableFrom(t))
    .ToList();

foreach (var type in actionResultTypes)
{
    Console.WriteLine(type.FullName);
}

// 2. IActionResult를 직접 구현하는 모든 클래스 찾기 (ActionResult 제외)
var iActionResultTypes = typeof(IActionResult).Assembly
    .GetTypes()
    .Where(t => t.IsClass &amp;&amp; !t.IsAbstract &amp;&amp; typeof(IActionResult).IsAssignableFrom(t) &amp;&amp; !typeof(ActionResult).IsAssignableFrom(t))
    .ToList();</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Microsoft.AspNetCore.Mvc.AcceptedAtActionResult
Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult
Microsoft.AspNetCore.Mvc.AcceptedResult
Microsoft.AspNetCore.Mvc.ActionResult
Microsoft.AspNetCore.Mvc.AntiforgeryValidationFailedResult
Microsoft.AspNetCore.Mvc.BadRequestObjectResult
Microsoft.AspNetCore.Mvc.BadRequestResult
Microsoft.AspNetCore.Mvc.ChallengeResult
Microsoft.AspNetCore.Mvc.ConflictObjectResult
Microsoft.AspNetCore.Mvc.ConflictResult
Microsoft.AspNetCore.Mvc.ContentResult
Microsoft.AspNetCore.Mvc.CreatedAtActionResult
Microsoft.AspNetCore.Mvc.CreatedAtRouteResult
Microsoft.AspNetCore.Mvc.CreatedResult
Microsoft.AspNetCore.Mvc.EmptyResult
Microsoft.AspNetCore.Mvc.FileContentResult
Microsoft.AspNetCore.Mvc.FileResult
Microsoft.AspNetCore.Mvc.FileStreamResult
Microsoft.AspNetCore.Mvc.ForbidResult
Microsoft.AspNetCore.Mvc.HttpActionResult
Microsoft.AspNetCore.Mvc.JsonResult
Microsoft.AspNetCore.Mvc.LocalRedirectResult
Microsoft.AspNetCore.Mvc.NoContentResult
Microsoft.AspNetCore.Mvc.NotFoundObjectResult
Microsoft.AspNetCore.Mvc.NotFoundResult
Microsoft.AspNetCore.Mvc.ObjectResult
Microsoft.AspNetCore.Mvc.OkObjectResult
Microsoft.AspNetCore.Mvc.OkResult
Microsoft.AspNetCore.Mvc.PhysicalFileResult
Microsoft.AspNetCore.Mvc.RedirectResult
Microsoft.AspNetCore.Mvc.RedirectToActionResult
Microsoft.AspNetCore.Mvc.RedirectToPageResult
Microsoft.AspNetCore.Mvc.RedirectToRouteResult
Microsoft.AspNetCore.Mvc.SignInResult
Microsoft.AspNetCore.Mvc.SignOutResult
Microsoft.AspNetCore.Mvc.StatusCodeResult
Microsoft.AspNetCore.Mvc.UnauthorizedObjectResult
Microsoft.AspNetCore.Mvc.UnauthorizedResult
Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult
Microsoft.AspNetCore.Mvc.UnprocessableEntityResult
Microsoft.AspNetCore.Mvc.UnsupportedMediaTypeResult
Microsoft.AspNetCore.Mvc.VirtualFileResult</pre>
<p>The post <a href="https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/">리플렉션(Reflection)을 사용한 ActionResult를 상속받는 모든 클래스 찾기</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/%eb%a6%ac%ed%94%8c%eb%a0%89%ec%85%98reflection%ec%9d%84-%ec%82%ac%ec%9a%a9%ed%95%9c-actionresult%eb%a5%bc-%ec%83%81%ec%86%8d%eb%b0%9b%eb%8a%94-%eb%aa%a8%eb%93%a0-%ed%81%b4%eb%9e%98%ec%8a%a4/40153/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core Route Constraints</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Mon, 28 Jul 2025 04:02:09 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET Development]]></category>
		<category><![CDATA[Action Method]]></category>
		<category><![CDATA[alpha]]></category>
		<category><![CDATA[Alphabetic]]></category>
		<category><![CDATA[alphanum]]></category>
		<category><![CDATA[Alphanumeric]]></category>
		<category><![CDATA[API Development]]></category>
		<category><![CDATA[API 개발]]></category>
		<category><![CDATA[app.UseEndpoints()]]></category>
		<category><![CDATA[app.UseRouting()]]></category>
		<category><![CDATA[Application Routing]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Attribute Routing]]></category>
		<category><![CDATA[Backend Development]]></category>
		<category><![CDATA[bool]]></category>
		<category><![CDATA[Boolean]]></category>
		<category><![CDATA[Catch-all parameter]]></category>
		<category><![CDATA[Conditional Routing]]></category>
		<category><![CDATA[Constraint]]></category>
		<category><![CDATA[Constraint Mapping]]></category>
		<category><![CDATA[Constraint Pattern]]></category>
		<category><![CDATA[ConstraintMap]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Convention-based Routing]]></category>
		<category><![CDATA[custom]]></category>
		<category><![CDATA[datetime]]></category>
		<category><![CDATA[decimal]]></category>
		<category><![CDATA[Default Value]]></category>
		<category><![CDATA[double]]></category>
		<category><![CDATA[Dynamic Path]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[Endpoint Routing]]></category>
		<category><![CDATA[Explicit Routing]]></category>
		<category><![CDATA[Extensibility]]></category>
		<category><![CDATA[float]]></category>
		<category><![CDATA[guid]]></category>
		<category><![CDATA[HTTP Methods]]></category>
		<category><![CDATA[HTTP Request]]></category>
		<category><![CDATA[HTTP 메서드]]></category>
		<category><![CDATA[HTTP 요청]]></category>
		<category><![CDATA[HttpContext]]></category>
		<category><![CDATA[IEndpointRouteBuilder]]></category>
		<category><![CDATA[Implicit Routing]]></category>
		<category><![CDATA[int]]></category>
		<category><![CDATA[Integer]]></category>
		<category><![CDATA[IRouteConstraint]]></category>
		<category><![CDATA[Lambda Handler]]></category>
		<category><![CDATA[Length]]></category>
		<category><![CDATA[length()]]></category>
		<category><![CDATA[long]]></category>
		<category><![CDATA[MapGet()]]></category>
		<category><![CDATA[MapPost()]]></category>
		<category><![CDATA[max()]]></category>
		<category><![CDATA[Maximum]]></category>
		<category><![CDATA[Maximum Length]]></category>
		<category><![CDATA[maxlength()]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[min()]]></category>
		<category><![CDATA[Minimal API]]></category>
		<category><![CDATA[Minimum]]></category>
		<category><![CDATA[Minimum Length]]></category>
		<category><![CDATA[minlength()]]></category>
		<category><![CDATA[MVC Pattern]]></category>
		<category><![CDATA[MVC 패턴]]></category>
		<category><![CDATA[Optional Parameter]]></category>
		<category><![CDATA[Parameter Binding]]></category>
		<category><![CDATA[Path Constraint]]></category>
		<category><![CDATA[Path Matching]]></category>
		<category><![CDATA[Placeholder]]></category>
		<category><![CDATA[range()]]></category>
		<category><![CDATA[Razor Pages]]></category>
		<category><![CDATA[Regex]]></category>
		<category><![CDATA[Request Processing]]></category>
		<category><![CDATA[Request Routing]]></category>
		<category><![CDATA[required]]></category>
		<category><![CDATA[Required Parameter]]></category>
		<category><![CDATA[RESTful API]]></category>
		<category><![CDATA[Route Builder]]></category>
		<category><![CDATA[Route Constraint]]></category>
		<category><![CDATA[Route Control]]></category>
		<category><![CDATA[Route Data]]></category>
		<category><![CDATA[Route Definition]]></category>
		<category><![CDATA[Route Handler]]></category>
		<category><![CDATA[Route Options]]></category>
		<category><![CDATA[Route Parameter]]></category>
		<category><![CDATA[Route Parameter Validation]]></category>
		<category><![CDATA[Route Priority]]></category>
		<category><![CDATA[Route Restriction]]></category>
		<category><![CDATA[Route Template]]></category>
		<category><![CDATA[RouteValueDictionary]]></category>
		<category><![CDATA[Routing Configuration]]></category>
		<category><![CDATA[Routing Engine]]></category>
		<category><![CDATA[Routing Middleware]]></category>
		<category><![CDATA[Routing Rules]]></category>
		<category><![CDATA[Routing System]]></category>
		<category><![CDATA[routing table]]></category>
		<category><![CDATA[SEO Friendly]]></category>
		<category><![CDATA[SEO 친화적]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[URL Constraint]]></category>
		<category><![CDATA[URL Design]]></category>
		<category><![CDATA[URL Matching]]></category>
		<category><![CDATA[URL Parsing]]></category>
		<category><![CDATA[URL Path Variable]]></category>
		<category><![CDATA[URL Segment]]></category>
		<category><![CDATA[URL Structure]]></category>
		<category><![CDATA[URL Validity]]></category>
		<category><![CDATA[URL 경로 변수]]></category>
		<category><![CDATA[URL 구조]]></category>
		<category><![CDATA[URL 매칭]]></category>
		<category><![CDATA[URL 설계]]></category>
		<category><![CDATA[URL 세그먼트]]></category>
		<category><![CDATA[URL 유효성]]></category>
		<category><![CDATA[URL 파싱]]></category>
		<category><![CDATA[Validation]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Framework]]></category>
		<category><![CDATA[경로 매칭]]></category>
		<category><![CDATA[경로 일치]]></category>
		<category><![CDATA[경로 제약]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[규칙 기반 라우팅]]></category>
		<category><![CDATA[기본값]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[동적 경로]]></category>
		<category><![CDATA[라우트]]></category>
		<category><![CDATA[라우트 데이터]]></category>
		<category><![CDATA[라우트 빌더]]></category>
		<category><![CDATA[라우트 옵션]]></category>
		<category><![CDATA[라우트 우선순위]]></category>
		<category><![CDATA[라우트 정의]]></category>
		<category><![CDATA[라우트 제약]]></category>
		<category><![CDATA[라우트 제어]]></category>
		<category><![CDATA[라우트 제한]]></category>
		<category><![CDATA[라우트 템플릿]]></category>
		<category><![CDATA[라우트 파라미터 유효성]]></category>
		<category><![CDATA[라우트 핸들러]]></category>
		<category><![CDATA[라우팅]]></category>
		<category><![CDATA[라우팅 규칙]]></category>
		<category><![CDATA[라우팅 매개변수]]></category>
		<category><![CDATA[라우팅 미들웨어]]></category>
		<category><![CDATA[라우팅 설정]]></category>
		<category><![CDATA[라우팅 시스템]]></category>
		<category><![CDATA[라우팅 엔진]]></category>
		<category><![CDATA[라우팅 테이블]]></category>
		<category><![CDATA[람다 핸들러]]></category>
		<category><![CDATA[매개변수 바인딩]]></category>
		<category><![CDATA[명시적 라우팅]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[백엔드 개발]]></category>
		<category><![CDATA[사용자 지정]]></category>
		<category><![CDATA[선택적 매개변수]]></category>
		<category><![CDATA[속성 라우팅]]></category>
		<category><![CDATA[암시적 라우팅]]></category>
		<category><![CDATA[애플리케이션 라우팅]]></category>
		<category><![CDATA[액션 메서드]]></category>
		<category><![CDATA[엔드포인트]]></category>
		<category><![CDATA[엔드포인트 라우팅]]></category>
		<category><![CDATA[요청 라우팅]]></category>
		<category><![CDATA[요청 처리]]></category>
		<category><![CDATA[웹 애플리케이션]]></category>
		<category><![CDATA[웹 프레임워크]]></category>
		<category><![CDATA[유효성 검사]]></category>
		<category><![CDATA[정규식]]></category>
		<category><![CDATA[제약 조건 매핑]]></category>
		<category><![CDATA[제약 조건 패턴]]></category>
		<category><![CDATA[제한 조건]]></category>
		<category><![CDATA[조건부 라우팅]]></category>
		<category><![CDATA[캐치올 매개변수]]></category>
		<category><![CDATA[컨트롤러]]></category>
		<category><![CDATA[플레이스홀더]]></category>
		<category><![CDATA[확장성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40140</guid>

					<description><![CDATA[<p>🔥 ASP.NET Core Route Constraints (라우트 제한 조건) https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0 ASP.NET Core의 라우팅 시스템에서 라우트 제한 조건(Route Constraints)은 URL 경로의 특정 세그먼트(라우팅 매개변수)에 대한 유효성 검사 규칙을 정의하는 강력한 기능입니다. 이를 통해 특정 URL 패턴이 매칭되기 위한 조건을 명시하고, 잘못된 형식의 URL 요청이 특정 라우트에 매칭되는 것을 방지할 수 있습니다. 1️⃣ 라우트 제한 조건의 필요성 1. [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/">ASP.NET Core Route Constraints</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-ce724247      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-route-constraints-라우트-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Route Constraints (라우트 제한 조건)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-라우트-제한-조건의-필요성" class="uagb-toc-link__trigger">1&#x20e3; 라우트 제한 조건의 필요성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-라우트-제한-조건-정의-방법" class="uagb-toc-link__trigger">2&#x20e3; 라우트 제한 조건 정의 방법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-주요-라우트-제한-조건-종류-및-예시" class="uagb-toc-link__trigger">3&#x20e3; 주요 라우트 제한 조건 종류 및 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#타입-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 타입 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#길이-및-범위-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 길이 및 범위 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#정규식-기반-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정규식 기반 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#다른-일반적인-제한-조건" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다른 일반적인 제한 조건</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#사용자-지정-라우트-제한-조건-custom-route-constraints" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용자 지정 라우트 제한 조건 (Custom Route Constraints)</a></li></ul><li class="uagb-toc__list"><a href="#4-endpoint-selection-order-엔드포인트-선택-순서" class="uagb-toc-link__trigger">4&#x20e3; Endpoint Selection Order (엔드포인트 선택 순서)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#명시적-순서-order-property" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 명시적 순서 (Order Property)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#라우트-템플릿의-구체성-specificity" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 라우트 템플릿의 구체성 (Specificity):</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#정의-순서-declaration-order" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정의 순서 (Declaration Order):</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Route Constraints (라우트 제한 조건)</h2>



<p><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-8.0</a></p>



<p>ASP.NET Core의 라우팅 시스템에서 <strong>라우트 제한 조건(Route Constraints)</strong>은 URL 경로의 특정 세그먼트(라우팅 매개변수)에 대한 유효성 검사 규칙을 정의하는 강력한 기능입니다. </p>



<p>이를 통해 특정 URL 패턴이 매칭되기 위한 조건을 명시하고, 잘못된 형식의 URL 요청이 특정 라우트에 매칭되는 것을 방지할 수 있습니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 라우트 제한 조건의 필요성</h3>



<p>1. <strong>정확한 라우트 매칭</strong>: <br>동일한 경로 프리픽스를 가지는 여러 라우트 중에서 가장 적절한 라우트를 선택할 수 있도록 돕습니다.</p>



<ul class="wp-block-list">
<li>예: <code>/products/123</code> (ID로 조회)와 <code>/products/shoes</code> (이름으로 조회)를 구분</li>
</ul>



<p>2. <strong>유효성 검사</strong>: <br>특정 매개변수가 예상하는 형식(예: 숫자, GUID, 날짜)인지 강제하여, 컨트롤러 액션 또는 핸들러에서 타입 변환 오류를 줄입니다.</p>



<p>3. <strong>보안 및 견고성</strong>: <br>잘못된 형식의 입력으로 인한 잠재적인 오류나 공격 시도를 줄이는 데 기여합니다.</p>



<p>4. <strong>URL 디자인 강화</strong>: <br>더욱 명확하고 의미론적인 URL 구조를 유지할 수 있도록 돕습니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 라우트 제한 조건 정의 방법</h3>



<p>라우트 제한 조건은 라우트 템플릿 내에서 라우팅 매개변수 이름 뒤에 <strong>콜론(<code>:</code>)을 붙이고 제한 조건 이름을 지정하여 정의</strong>합니다.</p>



<p>{매개변수명:제한조건이름}</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs 또는 Startup.cs의 UseEndpoints 블록
endpoints.MapGet("/products/{id:int}", (int id) => Results.Ok($"Product ID: {id}"));</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 주요 라우트 제한 조건 종류 및 예시</h3>



<p>ASP.NET Core는 다양한 내장 라우트 제한 조건을 제공합니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 타입 기반 제한 조건</h4>



<p>ASP.NET Core는 다양한 내장 라우트 제한 조건을 제공합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>int</td><td>32비트 정수로 매칭됩니다.</td><td>{id:int}</td><td>/items/123</td><td>/items/abc, /items/1.5</td></tr><tr><td>bool</td><td>불리언 값(true/false)으로 매칭됩니다.</td><td>{isActive:bool}</td><td>/status/true</td><td>/status/other</td></tr><tr><td>datetime</td><td>DateTime 형식으로 매칭됩니다.</td><td>{date:datetime}</td><td>/events/2023-10-26</td><td>/events/today</td></tr><tr><td>decimal</td><td>decimal 형식으로 매칭됩니다.</td><td>{price:decimal}</td><td>/products/99.99</td><td>/products/abc</td></tr><tr><td>double</td><td>double 형식으로 매칭됩니다.</td><td>{value:double}</td><td>/data/3.14159</td><td>/data/xyz</td></tr><tr><td>float</td><td>float 형식으로 매칭됩니다.</td><td>{measurement:float}</td><td>/sensor/0.5f</td><td>/sensor/abc</td></tr><tr><td>guid</td><td>GUID(Globally Unique Identifier)로 매칭됩니다.</td><td>{itemId:guid}</td><td>/item/a1b2c3d4-e5f6-7890-1234-567890abcdef</td><td>/item/invalid-guid</td></tr><tr><td>long</td><td>64비트 정수로 매칭됩니다.</td><td>{userId:long}</td><td>/users/9876543210</td><td>/users/short</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs 또는 Startup.cs
app.UseEndpoints(endpoints =>
{
    // 정수형 ID만 허용
    endpoints.MapGet("/products/{id:int}", (int id) => Results.Ok($"Getting product by ID: {id}"));

    // GUID 형식의 아이템 ID만 허용
    endpoints.MapGet("/items/{itemId:guid}", (Guid itemId) => Results.Ok($"Getting item by GUID: {itemId}"));

    // 특정 날짜 형식의 이벤트만 허용
    endpoints.MapGet("/events/on/{date:datetime}", (DateTime date) => Results.Ok($"Events on: {date.ToShortDateString()}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 길이 및 범위 기반 제한 조건</h4>



<p>매개변수 값의 길이 또는 숫자 범위를 제한합니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>min(value)</td><td>최소값 value 이상이어야 합니다.</td><td>{age:min(18)}</td><td>/users/age/18, /users/age/25</td><td>/users/age/17, /users/age/abc</td></tr><tr><td>max(value)</td><td>최대값 value 이하여야 합니다.</td><td>{quantity:max(100)}</td><td>/order/items/50</td><td>/order/items/101</td></tr><tr><td>range(min,max)</td><td>min과 max 사이의 값이어야 합니다.</td><td>{year:range(2000,2024)}</td><td>/posts/2023</td><td>/posts/1999, /posts/2025</td></tr><tr><td>minlength(length)</td><td>최소 length 길이 이상이어야 합니다.</td><td>{code:minlength(3)}</td><td>/code/abc, /code/abcd</td><td>/code/ab</td></tr><tr><td>maxlength(length)</td><td>최대 length 길이 이하여야 합니다.</td><td>{name:maxlength(10)}</td><td>/user/john</td><td>/user/someverylongname</td></tr><tr><td>length(min,max)</td><td>min과 max 사이의 길이어야 합니다.</td><td>{zip:length(5,9)}</td><td>/zip/12345, /zip/12345-6789</td><td>/zip/123, /zip/1234567890</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 연령이 18세 이상인 사용자만 허용
    endpoints.MapGet("/users/age/{age:min(18)}", (int age) => Results.Ok($"Adult user, age: {age}"));

    // 재고 수량이 10개에서 100개 사이인 경우에만 유효
    endpoints.MapGet("/inventory/{count:range(10,100)}", (int count) => Results.Ok($"Checking inventory count: {count}"));

    // 최소 5자, 최대 10자의 사용자 이름만 허용
    endpoints.MapGet("/profile/{username:length(5,10)}", (string username) => Results.Ok($"Profile for user: {username}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 정규식 기반 제한 조건</h4>



<p>정규 표현식(Regular Expression)을 사용하여 매개변수 값의 패턴을 지정합니다. </p>



<p>이는 가장 유연한 제한 조건입니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>regex(pattern)</td><td>pattern 정규식과 일치해야 합니다.</td><td>{code:regex(^[A-Z]{3}\\d{4}$)}</td><td>/item/ABC1234</td><td>/item/abc1234, /item/ABC123</td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // "abc"로 시작하고 숫자로 끝나는 코드만 허용
    endpoints.MapGet("/validate/{code:regex(abc\\d+)}", (string code) => Results.Ok($"Validated code: {code}"));

    // 상품 코드가 "PROD-"로 시작하고 4자리 숫자로 끝나는 경우만 허용
    endpoints.MapGet("/productcodes/{productCode:regex(^PROD-\\d{{4}}$)}", (string productCode) => Results.Ok($"Valid product code: {productCode}"));
    // 참고: 정규식 내의 중괄호는 이스케이프 필요 ({{, }})
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다른 일반적인 제한 조건</h4>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>제한 조건</td><td>설명</td><td>예시</td><td>매칭되는 URL</td><td>매칭되지 않는 URL</td></tr><tr><td>alpha</td><td>알파벳 문자(a-z, A-Z)만 허용합니다.</td><td>{name:alpha}</td><td>/users/john</td><td>/users/john123</td></tr><tr><td>alphanum</td><td>알파벳 문자 또는 숫자만 허용합니다.</td><td>{token:alphanum}</td><td>/api/token/abc123</td><td>/api/token/abc-123</td></tr><tr><td>required</td><td>매개변수가 반드시 존재해야 합니다. (?와 함께 사용 불가)</td><td>{country:required}</td><td>/location/korea</td><td>/location/</td></tr><tr><td>url</td><td>유효한 URL 형식이어야 합니다.</td><td>{redirectUrl:url}</td><td>/redirect/https://google.com</td><td>/redirect/not-a-url</td></tr><tr><td>minlength, maxlength, length</td><td>string 형식에 대한 길이 제한</td><td>위 &#8220;길이 및 범위 기반&#8221; 섹션 참고</td><td></td><td></td></tr></tbody></table></figure>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 이름은 알파벳만 허용
    endpoints.MapGet("/greet/{name:alpha}", (string name) => Results.Ok($"Hello, {name}!"));

    // 토큰은 알파벳 또는 숫자만 허용
    endpoints.MapGet("/verify/{token:alphanum}", (string token) => Results.Ok($"Verifying token: {token}"));

    // 국가 매개변수는 반드시 제공되어야 함
    endpoints.MapGet("/country/{country:required}", (string country) => Results.Ok($"Selected country: {country}"));
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용자 지정 라우트 제한 조건 (Custom Route Constraints)</h4>



<p>내장된 제한 조건만으로는 부족할 때, <code>IRouteConstraint</code> 인터페이스를 구현하여 자신만의 커스텀 라우트 제한 조건을 만들 수 있습니다.</p>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>IRouteConstraint</code> 인터페이스 구현</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Globalization;

public class CustomYearConstraint : IRouteConstraint
{
    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        // routeKey는 매개변수 이름 (예: "year")
        // values는 모든 라우팅 매개변수와 그 값들을 담고 있는 딕셔너리
        if (values.TryGetValue(routeKey, out object? value))
        {
            if (value is string yearString &amp;&amp; int.TryParse(yearString, out int year))
            {
                // 2000년 이후의 연도만 허용하는 예시
                return year >= 2000 &amp;&amp; year &lt;= DateTime.Now.Year + 1;
            }
        }
        return false;
    }
}</pre>



<div style="height:10px" aria-hidden="true" class="wp-block-spacer"></div>



<p>제한 조건 등록</p>



<p><code>Startup.cs</code>의 <code>ConfigureServices</code> 또는 <code>Program.cs</code>에서 <code>AddRouting</code> 메서드를 사용하여 커스텀 제한 조건을 등록합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRouting(options =>
{
    options.ConstraintMap.Add("customYear", typeof(CustomYearConstraint));
});

var app = builder.Build();

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    // /articles/2023 와 같이 customYear 제한 조건을 사용
    endpoints.MapGet("/articles/{year:customYear}", (int year) => Results.Ok($"Articles from year: {year}"));
});

app.Run();

//  사용 예시:
//  /articles/2023 -> 매칭됨
//  /articles/1999 -> 매칭되지 않음
//  /articles/2026 (현재 연도가 2025년이라면) -> 매칭되지 않음 (DateTime.Now.Year + 1 조건에 따라)</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; Endpoint Selection Order (엔드포인트 선택 순서)</h3>



<p>라우팅 시스템이 여러 엔드포인트 중 어떤 엔드포인트를 현재 요청에 매칭시킬지 결정하는 방식에 대한 내용입니다. </p>



<p>이는 라우트 제한 조건(Route Constraints)의 <strong>라우트 순서</strong>와 밀접하게 관련되어 있습니다.</p>



<p>ASP.NET Core의 Endpoint Selection Order는 주로 다음 원칙에 따라 이루어집니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>명시적 순서 (Order Property)</strong></h4>



<p>가장 직접적인 제어 방법은 엔드포인트에 <code>Order</code> 속성을 부여하는 것입니다. 숫자가 낮을수록 우선순위가 높습니다. </p>



<p>이는 <code>MapControllerRoute</code>나 <code>MapRazorPages</code>와 같은 메서드에는 직접 적용하기 어렵고, 주로 <code>MapGet</code> 등의 Minimal API 엔드포인트나 사용자 지정 <code>RouteEndpoint</code>를 만들 때 사용됩니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // Order가 -10인 엔드포인트 (가장 높은 우선순위)
    app.MapGet("/priority-test", () => "High priority").WithOrder(-10);

    // Order가 0인 기본 엔드포인트 (기본값)
    endpoints.MapGet("/test", () => "Normal priority");
});</pre>



<p>Note: <br><code>WithMetadata</code>를 통해 <code>RouteEndpointBuilder</code>에 직접 <code>Order</code>를 지정하는 것은 일반적인 사용법은 아니며, 실제로는 내부적으로 복잡한 로직을 통해 우선순위가 결정되거나 <code>RouteEndpoint.Order</code> 속성이 설정됩니다. <br>예를 들어, <code>MapRazorPages</code>나 <code>MapControllers</code>에서 생성되는 엔드포인트는 내부적으로 우선순위를 가집니다.)</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>라우트 템플릿의 구체성 (Specificity)</strong>:</strong></h4>



<p>이것이 가장 일반적이고 중요한 순서 결정 기준입니다. </p>



<p>라우팅 시스템은 요청 URL과 가장 &#8220;구체적으로&#8221; 일치하는 라우트 템플릿을 선호합니다. </p>



<p>구체성은 다음과 같은 요소에 의해 결정됩니다:</p>



<p><strong>리터럴 세그먼트의 수</strong>: 더 많은 리터럴(고정된 문자열) 세그먼트를 포함하는 라우트가 더 높은 우선순위를 가집니다.</p>



<ul class="wp-block-list">
<li><code>/products/all</code> (2개의 리터럴)이 <code>/products/{id}</code> (1개의 리터럴, 1개의 매개변수)보다 우선순위가 높습니다.</li>
</ul>



<p><strong>라우트 제한 조건의 사용</strong>: 제한 조건이 있는 매개변수는 제한 조건이 없는 매개변수보다 더 구체적인 것으로 간주됩니다.</p>



<ul class="wp-block-list">
<li><code>/products/{id:int}</code>는 <code>/products/{id}</code>보다 우선순위가 높습니다. (<code>/products/123</code>이 주어지면, <code>id:int</code>가 먼저 매칭됨)</li>
</ul>



<p><strong>선택적 매개변수</strong>: 선택적 매개변수가 없는 라우트가 더 구체적인 것으로 간주됩니다.</p>



<ul class="wp-block-list">
<li><code>/users/{id}</code>가 <code>/users/{id?}</code>보다 우선순위가 높습니다.</li>
</ul>



<p><strong>Catch-all 매개변수 (<code>*</code> 또는 <code>**</code>)</strong>: 가장 덜 구체적인 것으로 간주되어 거의 항상 마지막에 매칭됩니다.</p>



<ul class="wp-block-list">
<li><code>/files/{*path}</code>는 일반적으로 다른 모든 구체적인 라우트 뒤에 위치해야 합니다.</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong><strong>정의 순서 (Declaration Order)</strong>:</strong></strong></h4>



<p>동일한 구체성을 가진 두 개 이상의 라우트가 있을 경우, <code>UseEndpoints</code> 또는 <code>Map()</code> 계열 메서드에서 <strong>먼저 정의된 라우트가 우선순위를 가집니다.</strong> </p>



<p>이 때문에 &#8220;라우트 순서&#8221; 섹션에서 언급했듯이, 더 구체적인 라우트를 항상 먼저 정의하는 것이 중요합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseEndpoints(endpoints =>
{
    // 이 라우트가 먼저 정의됨
    endpoints.MapGet("/products/special", () => "Special Product Page");

    // 이 라우트는 구체성이 동일하지만 나중에 정의됨
    endpoints.MapGet("/products/{name}", (string name) => $"Product by Name: {name}");

    // /products/special 요청 시, "Special Product Page"가 반환됨
    // 만약 순서가 바뀌면 "/products/{name}"에 매칭되어 "Product by Name: special"이 반환될 수 있음
});</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Endpoint Selection Order는 복합적인 요소들의 상호작용으로 결정됩니다. </strong></p>



<p><strong>라우트 제한 조건은 이 선택 과정에서 라우트의 &#8220;구체성&#8221;을 높여 특정 URL 패턴에 대한 우선순위를 부여하는 핵심적인 방법입니다.</strong> </p>



<p>개발자는 이러한 원칙을 이해하고 라우트를 신중하게 정의하여 예상치 못한 라우팅 문제를 방지해야 합니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/">ASP.NET Core Route Constraints</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-route-constraints/40140/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>리버스 프록시(Reverse Proxy) 개념</title>
		<link>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/</link>
					<comments>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Sun, 27 Jul 2025 17:54:12 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[기타]]></category>
		<category><![CDATA[A/B Testing]]></category>
		<category><![CDATA[A/B 테스트]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[API Gateway]]></category>
		<category><![CDATA[API 게이트웨이]]></category>
		<category><![CDATA[Application Proxy]]></category>
		<category><![CDATA[Asynchronous Processing]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Backend Server]]></category>
		<category><![CDATA[Caching]]></category>
		<category><![CDATA[CDN]]></category>
		<category><![CDATA[Certificate Management]]></category>
		<category><![CDATA[client]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Compression]]></category>
		<category><![CDATA[Container]]></category>
		<category><![CDATA[Content Delivery]]></category>
		<category><![CDATA[Content Delivery Network]]></category>
		<category><![CDATA[Cookie Management]]></category>
		<category><![CDATA[DDoS Protection]]></category>
		<category><![CDATA[DDoS 방어]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Distributed Processing]]></category>
		<category><![CDATA[Edge Computing]]></category>
		<category><![CDATA[Envoy]]></category>
		<category><![CDATA[Error Page]]></category>
		<category><![CDATA[firewall]]></category>
		<category><![CDATA[Gateway]]></category>
		<category><![CDATA[Gateway Pattern]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[Header Manipulation]]></category>
		<category><![CDATA[High Availability]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[HTTP/2]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[Inbound Traffic]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Intermediary]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[L7 스위치]]></category>
		<category><![CDATA[Layer 7 Switch]]></category>
		<category><![CDATA[Load Balancing]]></category>
		<category><![CDATA[Log Analysis]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[MSA]]></category>
		<category><![CDATA[Multi-tenancy]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Offloading]]></category>
		<category><![CDATA[On-premise]]></category>
		<category><![CDATA[Operations]]></category>
		<category><![CDATA[Outbound Proxy]]></category>
		<category><![CDATA[Performance Optimization]]></category>
		<category><![CDATA[Protocol Conversion]]></category>
		<category><![CDATA[Proxy]]></category>
		<category><![CDATA[QUIC]]></category>
		<category><![CDATA[Request Forwarding]]></category>
		<category><![CDATA[Request Routing]]></category>
		<category><![CDATA[Response]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Scalability]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Security Layer]]></category>
		<category><![CDATA[Server Protection]]></category>
		<category><![CDATA[Service Discovery]]></category>
		<category><![CDATA[Service Mesh]]></category>
		<category><![CDATA[Service Proxy]]></category>
		<category><![CDATA[Session Persistence]]></category>
		<category><![CDATA[SSL Termination]]></category>
		<category><![CDATA[SSL 종료]]></category>
		<category><![CDATA[State Management]]></category>
		<category><![CDATA[Sticky Session]]></category>
		<category><![CDATA[System Architecture]]></category>
		<category><![CDATA[TLS Handshake]]></category>
		<category><![CDATA[TLS 핸드셰이크]]></category>
		<category><![CDATA[Traffic Management]]></category>
		<category><![CDATA[Traffic Splitting]]></category>
		<category><![CDATA[URL Rewriting]]></category>
		<category><![CDATA[URL 재작성]]></category>
		<category><![CDATA[User Experience]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[Virtual Hosting]]></category>
		<category><![CDATA[WAF]]></category>
		<category><![CDATA[Web Acceleration]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Application Firewall]]></category>
		<category><![CDATA[Web Cache]]></category>
		<category><![CDATA[Web Proxy]]></category>
		<category><![CDATA[Web Server]]></category>
		<category><![CDATA[Web Service]]></category>
		<category><![CDATA[가상 호스팅]]></category>
		<category><![CDATA[개발자]]></category>
		<category><![CDATA[게이트웨이]]></category>
		<category><![CDATA[게이트웨이 패턴]]></category>
		<category><![CDATA[고가용성]]></category>
		<category><![CDATA[네트워크]]></category>
		<category><![CDATA[데브옵스]]></category>
		<category><![CDATA[로그 분석]]></category>
		<category><![CDATA[로드 밸런싱]]></category>
		<category><![CDATA[리버스 프록시]]></category>
		<category><![CDATA[마이크로서비스]]></category>
		<category><![CDATA[멀티테넌시]]></category>
		<category><![CDATA[모니터링]]></category>
		<category><![CDATA[방화벽]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[백엔드 서버]]></category>
		<category><![CDATA[보안]]></category>
		<category><![CDATA[보안 계층]]></category>
		<category><![CDATA[부하 분산]]></category>
		<category><![CDATA[부하 분산 장치]]></category>
		<category><![CDATA[분산 처리]]></category>
		<category><![CDATA[비동기 처리]]></category>
		<category><![CDATA[사용자 경험]]></category>
		<category><![CDATA[상태 관리]]></category>
		<category><![CDATA[서버 보호]]></category>
		<category><![CDATA[서비스 디스커버리]]></category>
		<category><![CDATA[서비스 메쉬]]></category>
		<category><![CDATA[서비스 프록시]]></category>
		<category><![CDATA[성능 최적화]]></category>
		<category><![CDATA[세션 유지]]></category>
		<category><![CDATA[시스템 아키텍처]]></category>
		<category><![CDATA[아웃바운드 프록시]]></category>
		<category><![CDATA[압축]]></category>
		<category><![CDATA[애플리케이션 프록시]]></category>
		<category><![CDATA[에러 페이지]]></category>
		<category><![CDATA[엣지 컴퓨팅]]></category>
		<category><![CDATA[오프로딩]]></category>
		<category><![CDATA[온프레미스]]></category>
		<category><![CDATA[요청 라우팅]]></category>
		<category><![CDATA[요청 전달]]></category>
		<category><![CDATA[운영]]></category>
		<category><![CDATA[웹 가속]]></category>
		<category><![CDATA[웹 방화벽]]></category>
		<category><![CDATA[웹 서버]]></category>
		<category><![CDATA[웹 서비스]]></category>
		<category><![CDATA[웹 애플리케이션]]></category>
		<category><![CDATA[웹 캐시]]></category>
		<category><![CDATA[웹 프록시]]></category>
		<category><![CDATA[응답]]></category>
		<category><![CDATA[인가]]></category>
		<category><![CDATA[인바운드 트래픽]]></category>
		<category><![CDATA[인증]]></category>
		<category><![CDATA[인증서 관리]]></category>
		<category><![CDATA[인프라]]></category>
		<category><![CDATA[중간자]]></category>
		<category><![CDATA[캐싱]]></category>
		<category><![CDATA[컨테이너]]></category>
		<category><![CDATA[콘텐츠 전송]]></category>
		<category><![CDATA[쿠버네티스]]></category>
		<category><![CDATA[쿠키 관리]]></category>
		<category><![CDATA[클라우드]]></category>
		<category><![CDATA[클라이언트]]></category>
		<category><![CDATA[트래픽 관리]]></category>
		<category><![CDATA[트래픽 분할]]></category>
		<category><![CDATA[프로토콜 변환]]></category>
		<category><![CDATA[프록시]]></category>
		<category><![CDATA[헤더 조작]]></category>
		<category><![CDATA[확장성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40122</guid>

					<description><![CDATA[<p>❓ 리버스 프록시(Reverse Proxy) 리버스 프록시(Reverse Proxy) 서버는 인터넷으로부터의 클라이언트 요청을 받아 백엔드 서버(실제 콘텐츠나 애플리케이션을 호스팅하는 서버)로 전달하고, 백엔드 서버로부터 받은 응답을 다시 클라이언트에게 전달하는 서버입니다. 정리하자면 웹 서비스의 최전선에 위치하여 클라이언트와 실제 서버 사이의 중개자 역할 일반적인 포워드 프록시(Forward Proxy)가 클라이언트가 인터넷에 접속하기 위해 사용하는 것(예: 회사 네트워크에서 외부 웹사이트 접속)과는 반대로, 리버스 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/">리버스 프록시(Reverse Proxy) 개념</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-32d5faea      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#리버스-프록시reverse-proxy" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시(Reverse Proxy)</a><li class="uagb-toc__list"><a href="#리버스-프록시는-왜-사용하는가" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f300.png" alt="🌀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시는 왜 사용하는가?</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-보안-강화-security" class="uagb-toc-link__trigger">1&#x20e3; 보안 강화 (Security)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-로드-밸런싱-load-balancing" class="uagb-toc-link__trigger">2&#x20e3; 로드 밸런싱 (Load Balancing)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-성능-최적화-performance-optimization" class="uagb-toc-link__trigger">3&#x20e3; 성능 최적화 (Performance Optimization)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-쉬운-유지보수-및-확장-maintainability-scalability" class="uagb-toc-link__trigger">4&#x20e3; 쉬운 유지보수 및 확장 (Maintainability &amp; Scalability)</a></li></ul></li><li class="uagb-toc__list"><a href="#동작-방식-요약" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d9.png" alt="📙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 동작 방식 요약</a><li class="uagb-toc__list"><a href="#대표적인-리버스-프록시-소프트웨어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대표적인 리버스 프록시 소프트웨어</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시(Reverse Proxy)</h2>



<p><strong>리버스 프록시(Reverse Proxy)</strong> 서버는 인터넷으로부터의 클라이언트 요청을 받아 백엔드 서버(실제 콘텐츠나 애플리케이션을 호스팅하는 서버)로 전달하고, </p>



<p>백엔드 서버로부터 받은 응답을 다시 클라이언트에게 전달하는 서버입니다. </p>



<p>정리하자면 <strong>웹 서비스의 최전선에 위치하여 클라이언트와 실제 서버 사이의 중개자 역할</strong></p>



<p>일반적인 <strong>포워드 프록시(Forward Proxy)가 클라이언트가 인터넷에 접속하기 위해 사용하는 것(예: 회사 네트워크에서 외부 웹사이트 접속)</strong>과는 반대로, </p>



<p><strong>리버스 프록시(Reverse Proxy)는 서버 앞단에서 서버로 들어오는 요청을 처리</strong>합니다.</p>



<div class="wp-block-uagb-container uagb-block-fbf8b3c1 alignfull uagb-is-root-container"><div class="uagb-container-inner-blocks-wrap">
<div class="wp-block-uagb-container uagb-block-3a8748f6">
<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="720" src="https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_.png" alt="" class="wp-image-40123" srcset="https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_.png 1920w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-300x113.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-768x288.png 768w, https://lycos7560.com/wp-content/uploads/2025/07/Reverse_proxy_h2g2bob.svg_-1536x576.png 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">Reverse Proxy<br><a href="https://ko.wikipedia.org/wiki/%EB%A6%AC%EB%B2%84%EC%8A%A4_%ED%94%84%EB%A1%9D%EC%8B%9C#/media/%ED%8C%8C%EC%9D%BC:Reverse_proxy_h2g2bob.svg" target="_blank" rel="noreferrer noopener">https://ko.wikipedia.org/wiki/%EB%A6%AC%EB%B2%84%EC%8A%A4_%ED%94%84%EB%A1%9D%EC%8B%9C#/media/%ED%8C%8C%EC%9D%BC:Reverse_proxy_h2g2bob.svg</a></figcaption></figure>
</div>



<div class="wp-block-uagb-container uagb-block-68dc1c5c">
<figure class="wp-block-image size-full"><img decoding="async" width="1920" height="960" src="https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow.png" alt="" class="wp-image-40124" srcset="https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow.png 1920w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-300x150.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-768x384.png 768w, https://lycos7560.com/wp-content/uploads/2025/07/forward_proxy_flow-1536x768.png 1536w" sizes="(max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption"><strong>Forward Proxy</strong><br><a href="https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/" target="_blank" rel="noreferrer noopener">https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/</a></figcaption></figure>
</div>
</div></div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f300.png" alt="🌀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 리버스 프록시는 왜 사용하는가?</h2>



<p>리버스 프록시는 아래과 같은 주요 이점 때문에 웹 아키텍처에서 필수적인 요소로 자리를 잡았습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 보안 강화 (Security)</h3>



<ul class="wp-block-list">
<li><strong>직접적인 서버 노출 방지:</strong> <br>실제 백엔드 서버의 IP 주소나 내부 구조를 외부 클라이언트에게 숨겨 보안 위험을 줄입니다. 공격자는 리버스 프록시만 볼 수 있습니다.</li>



<li><strong>공격 방어:</strong> <br>DDoS 공격, SQL Injection, XSS 공격 등 다양한 웹 공격에 대한 1차 방어선 역할을 수행합니다. 수상한 트래픽을 필터링하거나 차단할 수 있습니다.</li>



<li><strong>SSL/TLS 암호화 종료 (SSL/TLS Termination):</strong> <br>클라이언트와의 암호화된 통신(HTTPS)을 리버스 프록시가 대신 처리하고, 백엔드 서버와의 통신은 암호화되지 않은 HTTP로 전환할 수 있습니다.<br>이는 백엔드 서버의 부하를 줄여주고, SSL 인증서 관리의 중앙화를 가능하게 합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 로드 밸런싱 (Load Balancing)</h3>



<ul class="wp-block-list">
<li>리버스 프록시는 여러 개의 백엔드 서버로 트래픽을 효율적으로 분산하여 <strong>특정 서버에 부하가 집중되는 것을 방지</strong>합니다. <br>이를 통해 서비스의 <strong>안정성과 가용성을 높이고, 대규모 트래픽을 처리할 수 있도록 확장성을 제공</strong>합니다.</li>



<li>서버 중 하나에 문제가 발생하면, 해당 서버로의 트래픽을 중단하고 정상 작동하는 다른 서버로 요청을 보낼 수 있습니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 성능 최적화 (Performance Optimization)</h3>



<ul class="wp-block-list">
<li><strong>캐싱 (Caching):</strong> <br>자주 요청되는 정적 콘텐츠(이미지, CSS, JavaScript 파일 등)를 리버스 프록시 서버에 캐싱하여, <br>백엔드 서버로의 요청을 줄이고 클라이언트에게 더 빠르게 응답할 수 있습니다.</li>



<li><strong>압축 (Compression):</strong> <br>클라이언트에게 보내는 응답 데이터를 압축하여 전송량을 줄이고 로딩 속도를 향상시킵니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 쉬운 유지보수 및 확장 (Maintainability &amp; Scalability)</h3>



<ul class="wp-block-list">
<li>백엔드 서버를 추가하거나 제거할 때, 클라이언트는 리버스 프록시의 주소만 알기 때문에 변경 사항을 알 필요가 없습니다. <br>이는 서버 아키텍처를 유연하게 관리할 수 있도록 해줍니다.</li>



<li>A/B 테스트, URL 재작성, 특정 요청 라우팅 등 복잡한 트래픽 관리 정책을 구현하기 용이합니다.</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d9.png" alt="📙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 동작 방식 요약</h2>



<ol class="wp-block-list">
<li><strong>클라이언트 요청:</strong> <br>웹 브라우저와 같은 클라이언트가 특정 웹 서비스(예: <code>www.example.com</code>)에 요청을 보냅니다.</li>



<li><strong>리버스 프록시 수신:</strong> <br>이 요청은 DNS 설정을 통해 <strong>리버스 프록시 서버</strong>로 전달됩니다.</li>



<li><strong>요청 처리 및 전달:</strong> <br>리버스 프록시 서버는 요청을 검사하고, 설정된 규칙(로드 밸런싱 알고리즘, URL 경로 등)에 따라 적절한 <strong>백엔드 서버</strong>를 선택하여 요청을 전달합니다. <br>(이때, SSL/TLS 종료가 이루어질 수 있습니다.)</li>



<li><strong>응답 수신 및 전달:</strong> <br>백엔드 서버는 요청을 처리하고 응답을 리버스 프록시 서버로 보냅니다. <br>리버스 프록시는 이 응답을 받아 (캐싱, 압축 등의 처리를 거쳐) 최종적으로 클라이언트에게 전달합니다.</li>
</ol>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60a.png" alt="😊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대표적인 리버스 프록시 소프트웨어</h2>



<ul class="wp-block-list">
<li><strong>Nginx (엔진엑스):</strong> <br>가장 널리 사용되는 웹 서버이자 리버스 프록시 서버입니다. 고성능과 경량성이 특징입니다.</li>



<li><strong>Apache HTTP Server (아파치):</strong> <br><code>mod_proxy</code> 모듈을 통해 리버스 프록시 기능을 제공합니다.</li>



<li><strong>HAProxy:</strong> <br>고가용성 로드 밸런싱 및 프록싱에 특화된 솔루션입니다.</li>



<li><strong>Envoy Proxy:</strong> <br>마이크로서비스 아키텍처에서 많이 사용되는 최신 프록시입니다.</li>
</ul>



<p></p>
<p>The post <a href="https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/">리버스 프록시(Reverse Proxy) 개념</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/etc/%eb%a6%ac%eb%b2%84%ec%8a%a4-%ed%94%84%eb%a1%9d%ec%8b%9creverse-proxy-%ea%b0%9c%eb%85%90/40122/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core의 미들웨어(Middleware)</title>
		<link>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/</link>
					<comments>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Thu, 24 Jul 2025 23:32:09 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[API Gateway]]></category>
		<category><![CDATA[API 게이트웨이]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[ASP.NET Core MVC]]></category>
		<category><![CDATA[async]]></category>
		<category><![CDATA[Asynchronous]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Await]]></category>
		<category><![CDATA[Caching]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Compression]]></category>
		<category><![CDATA[Conditional Middleware]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Configure]]></category>
		<category><![CDATA[Container]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Conventional Middleware]]></category>
		<category><![CDATA[CORS]]></category>
		<category><![CDATA[Cross-Origin Resource Sharing]]></category>
		<category><![CDATA[Custom Middleware]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[DELETE]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[Development Environment]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[DI]]></category>
		<category><![CDATA[Error Handling]]></category>
		<category><![CDATA[event]]></category>
		<category><![CDATA[Event-driven]]></category>
		<category><![CDATA[Exception Handling]]></category>
		<category><![CDATA[Extension Method]]></category>
		<category><![CDATA[Factory-based Middleware]]></category>
		<category><![CDATA[filter]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[HEAD]]></category>
		<category><![CDATA[Headers]]></category>
		<category><![CDATA[HTTP Context]]></category>
		<category><![CDATA[HTTP Method]]></category>
		<category><![CDATA[HTTP Request]]></category>
		<category><![CDATA[HTTP Response]]></category>
		<category><![CDATA[HTTP 메서드]]></category>
		<category><![CDATA[HTTP 요청]]></category>
		<category><![CDATA[HTTP 응답]]></category>
		<category><![CDATA[HTTP 컨텍스트]]></category>
		<category><![CDATA[HTTP/1.1]]></category>
		<category><![CDATA[HTTP/2]]></category>
		<category><![CDATA[HttpContext]]></category>
		<category><![CDATA[HTTPS Redirection]]></category>
		<category><![CDATA[HTTPS 리디렉션]]></category>
		<category><![CDATA[IApplicationBuilder]]></category>
		<category><![CDATA[IMiddleware]]></category>
		<category><![CDATA[Integration Testing]]></category>
		<category><![CDATA[Invoke]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Logging Provider]]></category>
		<category><![CDATA[Maintainability]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[Middleware Chain]]></category>
		<category><![CDATA[Minimal APIs]]></category>
		<category><![CDATA[Modularization]]></category>
		<category><![CDATA[next]]></category>
		<category><![CDATA[Non-Terminal Middleware]]></category>
		<category><![CDATA[Ops]]></category>
		<category><![CDATA[options]]></category>
		<category><![CDATA[Order Critical]]></category>
		<category><![CDATA[PATCH]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[POST]]></category>
		<category><![CDATA[Production Environment]]></category>
		<category><![CDATA[Program.cs]]></category>
		<category><![CDATA[Protocol]]></category>
		<category><![CDATA[PUT]]></category>
		<category><![CDATA[Request Body]]></category>
		<category><![CDATA[Request Flow]]></category>
		<category><![CDATA[Request Pipeline]]></category>
		<category><![CDATA[RequestDelegate]]></category>
		<category><![CDATA[Response Body]]></category>
		<category><![CDATA[Response Flow]]></category>
		<category><![CDATA[Reusability]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Run]]></category>
		<category><![CDATA[Scalability]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Separation of Concerns]]></category>
		<category><![CDATA[Service Container]]></category>
		<category><![CDATA[Short-circuit]]></category>
		<category><![CDATA[Single Responsibility Principle]]></category>
		<category><![CDATA[SoC]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[Startup]]></category>
		<category><![CDATA[Static Files]]></category>
		<category><![CDATA[Status Code]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Terminal Middleware]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Use]]></category>
		<category><![CDATA[UseWhen]]></category>
		<category><![CDATA[Web Application]]></category>
		<category><![CDATA[Web Application Framework]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Web Server]]></category>
		<category><![CDATA[Web Service]]></category>
		<category><![CDATA[WebApplication]]></category>
		<category><![CDATA[WebApplicationBuilder]]></category>
		<category><![CDATA[개발 환경]]></category>
		<category><![CDATA[개발자]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[관심사의 분리]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[단락]]></category>
		<category><![CDATA[단위 테스트]]></category>
		<category><![CDATA[단일 책임 원칙]]></category>
		<category><![CDATA[디버깅]]></category>
		<category><![CDATA[라우팅]]></category>
		<category><![CDATA[로깅]]></category>
		<category><![CDATA[로깅 프로바이더]]></category>
		<category><![CDATA[리버스 프록시]]></category>
		<category><![CDATA[마이크로서비스]]></category>
		<category><![CDATA[모듈화]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[미들웨어 체인]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[보안]]></category>
		<category><![CDATA[비동기]]></category>
		<category><![CDATA[비종료 미들웨어]]></category>
		<category><![CDATA[상태 코드]]></category>
		<category><![CDATA[서비스 메쉬]]></category>
		<category><![CDATA[서비스 컨테이너]]></category>
		<category><![CDATA[설정]]></category>
		<category><![CDATA[성능]]></category>
		<category><![CDATA[순서 중요]]></category>
		<category><![CDATA[압축]]></category>
		<category><![CDATA[액션]]></category>
		<category><![CDATA[예외 처리]]></category>
		<category><![CDATA[오류 처리]]></category>
		<category><![CDATA[요청 본문]]></category>
		<category><![CDATA[요청 파이프라인]]></category>
		<category><![CDATA[요청 흐름]]></category>
		<category><![CDATA[웹 개발]]></category>
		<category><![CDATA[웹 서버]]></category>
		<category><![CDATA[웹 서비스]]></category>
		<category><![CDATA[웹 애플리케이션]]></category>
		<category><![CDATA[웹 애플리케이션 프레임워크]]></category>
		<category><![CDATA[유지보수성]]></category>
		<category><![CDATA[응답 본문]]></category>
		<category><![CDATA[응답 흐름]]></category>
		<category><![CDATA[의존성 주입]]></category>
		<category><![CDATA[이벤트]]></category>
		<category><![CDATA[이벤트 드리븐]]></category>
		<category><![CDATA[인가]]></category>
		<category><![CDATA[인증]]></category>
		<category><![CDATA[재사용성]]></category>
		<category><![CDATA[정적 파일]]></category>
		<category><![CDATA[조건부 미들웨어]]></category>
		<category><![CDATA[종료 미들웨어]]></category>
		<category><![CDATA[최소 API]]></category>
		<category><![CDATA[캐싱]]></category>
		<category><![CDATA[커스텀 미들웨어]]></category>
		<category><![CDATA[컨벤셔널 미들웨어]]></category>
		<category><![CDATA[컨테이너]]></category>
		<category><![CDATA[컨트롤러]]></category>
		<category><![CDATA[쿠버네티스]]></category>
		<category><![CDATA[클라우드]]></category>
		<category><![CDATA[테스트]]></category>
		<category><![CDATA[통합 테스트]]></category>
		<category><![CDATA[파이프라인]]></category>
		<category><![CDATA[팩토리 기반 미들웨어]]></category>
		<category><![CDATA[프로덕션 환경]]></category>
		<category><![CDATA[프로토콜]]></category>
		<category><![CDATA[필터]]></category>
		<category><![CDATA[헤더]]></category>
		<category><![CDATA[확장 메서드]]></category>
		<category><![CDATA[확장성]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40130</guid>

					<description><![CDATA[<p>❓ 미들웨어(Middleware) https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0 ASP.NET Core의 미들웨어는 모든 HTTP 요청과 응답 파이프라인을 형성하는 일련의 구성 요소입니다. 각 미들웨어 구성 요소는 다음을 수행할 수 있습니다: 이러한 파이프라인을 통해 애플리케이션의 로직을 모듈화하고, 인증, 로깅, 오류 처리, 라우팅 등과 같은 기능을 깔끔하고 유지 관리하기 쉬운 방식으로 추가할 수 있습니다. ⛓️ 미들웨어 체인 (요청 파이프라인) ASP.NET Core 요청 파이프라인은 차례로 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/">ASP.NET Core의 미들웨어(Middleware)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-db9d164c      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어middleware" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어(Middleware)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어-체인-요청-파이프라인" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26d3.png" alt="⛓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 체인 (요청 파이프라인)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#요청-파이프라인-단락short-circuiting" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2702.png" alt="✂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 요청 파이프라인 단락(Short-circuiting)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#단락과-nextinvoke-이후-코드-실행" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단락과 next.Invoke() 이후 코드 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#경고-응답-전송-후-작업-주의" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 경고: 응답 전송 후 작업 주의</a></li></ul><li class="uagb-toc__list"><a href="#appuse-vs-apprun" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1fae0.png" alt="🫠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> app.Use vs. app.Run</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core의-커스텀-미들웨어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core의 커스텀 미들웨어</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#커스텀-미들웨어-클래스의-구조" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 미들웨어 클래스의 구조</a></li></ul><li class="uagb-toc__list"><a href="#커스텀-컨벤셔널-미들웨어-custom-conventional-middleware" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 컨벤셔널 미들웨어 (Custom Conventional Middleware)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#미들웨어-파이프라인의-이상적인-순서" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f500.png" alt="🔀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 파이프라인의 이상적인 순서</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen()</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen-작동-방식" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen() 작동 방식</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#usewhen-사용-시점" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f98b.png" alt="🦋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen() 사용 시점</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어(Middleware)</h2>



<p><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></p>



<p>ASP.NET Core의 <strong>미들웨어</strong>는 모든 HTTP 요청과 응답 <strong>파이프라인을 형성하는 일련의 구성 요소</strong>입니다.</p>



<p>각 미들웨어 구성 요소는 다음을 수행할 수 있습니다:</p>



<ul class="wp-block-list">
<li>들어오는 <strong>요청을 검사</strong>합니다.</li>



<li>요청 또는 응답을 <strong>수정</strong>합니다 (필요한 경우).</li>



<li>파이프라인의 다음 미들웨어를 <strong>호출</strong>하거나, 프로세스를 <strong>단락(short-circuit)</strong>시키고 자체적으로 응답을 생성합니다.</li>
</ul>



<p>이러한 파이프라인을 통해 <strong>애플리케이션의 로직을 모듈화하고, 인증, 로깅, 오류 처리, 라우팅 등과 </strong>같은 기능을 깔끔하고<strong> 유지 관리하기 쉬운 방식으로 추가</strong>할 수 있습니다.</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26d3.png" alt="⛓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 체인 (요청 파이프라인)</h3>



<p>ASP.NET Core 요청 파이프라인은 차례로 호출되는 일련의 요청 대리자로 구성됩니다.</p>



<p>요청 파이프라인을 일련의 연결된 파이프라고 상상해 보세요. </p>



<p>각 미들웨어 조각은 이 파이프라인의 밸브와 같아서, <strong>정보의 흐름을 제어하고 다양한 단계에서 특정 작업을 적용</strong>할 수 있습니다. </p>



<p>미들웨어를 등록하는 <strong>순서는 매우 중요</strong>하며, 등록된 순서대로 실행됩니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="607" height="389" src="https://lycos7560.com/wp-content/uploads/2025/07/image-12.png" alt="" class="wp-image-40131" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-12.png 607w, https://lycos7560.com/wp-content/uploads/2025/07/image-12-300x192.png 300w" sizes="(max-width: 607px) 100vw, 607px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p>각 델리게이트는 <strong>다음 델리게이트를 호출하기 전과 후에 작업</strong>을 수행할 수 있습니다. </p>



<p>예외 처리 델리게이트는 파이프라인의 후반 단계에서 발생하는 예외를 Catch할 수 있도록 파이프라인의 <strong>초기에 호출</strong>되어야 합니다.</p>



<p>가장 간단한 ASP.NET Core 앱은 모든 요청을 처리하는 <strong>단일 요청 델리게이트</strong>를 설정합니다. </p>



<p>이 경우에는 실제 요청 파이프라인이 포함되지 않습니다. </p>



<p>대신, 모든 HTTP 요청에 대한 응답으로 단일 익명 함수가 호출됩니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run(); // 이 app.Run은 위의 app.Run이 파이프라인을 종료하므로 실행되지 않습니다.</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><code>Use</code> 메서드를 사용하면 여러 요청 델리게이트를 체인으로 연결</strong>할 수 있습니다. </p>



<p>이때 <code>next</code> 매개변수는 파이프라인의 다음 델리게이트를 나타냅니다.</p>



<p><code>next</code> 매개변수를 호출하지 않음으로써 <strong>파이프라인을 단락(short-circuit)</strong>시킬 수 있습니다. </p>



<p>다음 예시에서 보여주듯이, 일반적으로 <code>next</code> 델리게이트를 호출하기 전과 후에 모두 작업을 수행할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var builder = WebApplication.CreateBuilder(args); // 웹 애플리케이션 빌더를 생성합니다.
var app = builder.Build(); // 빌더를 사용하여 웹 애플리케이션 인스턴스를 생성합니다.

// 첫 번째 미들웨어: app.Use()를 사용하여 파이프라인에 추가합니다.
app.Use(async (context, next) =>
{
    // 응답에 쓸 수 있는 작업을 수행합니다. (예: 헤더 추가, 응답의 시작 부분 작성)
    // 이 부분의 코드는 다음 미들웨어가 실행되기 전에 실행됩니다.

    await next.Invoke(); // 다음 미들웨어(이 경우 app.Run)를 호출하여 제어를 넘깁니다.

    // 응답에 쓰지 않는 로깅 또는 다른 작업을 수행합니다.
    // 이 부분의 코드는 다음 미들웨어가 실행된 후(응답이 생성된 후) 실행됩니다.
});

// 두 번째 미들웨어: app.Run()을 사용하여 파이프라인에 추가합니다.
// app.Run()은 파이프라인을 종료하는 미들웨어입니다.
app.Run(async context =>
{
    // 이 미들웨어는 "Hello from 2nd delegate."를 응답에 작성하고 파이프라인을 종료합니다.
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

// 이 app.Run()은 위의 app.Run()이 이미 파이프라인을 종료했기 때문에 실행되지 않습니다.
app.Run();</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2702.png" alt="✂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 요청 파이프라인 단락(Short-circuiting)</h3>



<p>ASP.NET Core의 미들웨어 파이프라인에서, 특정 델리게이트(미들웨어)가 요청을 <strong>다음 델리게이트로 전달하지 않을 때</strong> 이를 <strong>요청 파이프라인을 단락(short-circuiting)시킨다</strong>고 합니다. </p>



<p>파이프라인 단락은 불필요한 작업을 피할 수 있기 때문에 종종 유용합니다.</p>



<p>예를 들어, <strong>정적 파일 미들웨어(Static File Middleware)</strong>는 요청된 파일이 정적 파일(이미지, CSS, JavaScript 등)일 경우 해당 요청을 처리하고</p>



<p> 파이프라인의 나머지 부분을 단락시킴으로써 <strong>종료 미들웨어(terminal middleware)</strong> 역할을 할 수 있습니다. </p>



<p>이렇게 되면 정적 파일을 제공한 후에는 <strong>인증이나 라우팅과 같은 다른 미들웨어들이 실행될 필요가 없으므로 효율성이 높아집니다.</strong></p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단락과 <code>next.Invoke()</code> 이후 코드 실행</h4>



<p>파이프라인의 추가적인 처리를 종료시키는 미들웨어보다 <strong>앞서 파이프라인에 추가된 미들웨어</strong>는 여전히 <code>next.Invoke()</code> 구문 이후의 코드를 처리합니다. </p>



<p>즉, 비록 파이프라인이 단락되어 최종 응답이 더 이상 아래로 전달되지 않더라도, <code>next.Invoke()</code>를 호출했던 이전 미들웨어들은 응답이 완료된 후 자신의 후처리 로직을 실행할 수 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 경고: 응답 전송 후 작업 주의</h4>



<p><strong>응답이 클라이언트에게 이미 전송되었거나 전송되기 시작한 후에는 <code>next.Invoke()</code>를 호출하거나 응답에 쓰려고 시도하지 마세요.</strong> </p>



<p><code>HttpResponse</code>가 시작된 후에는 응답에 변경을 가하려고 하면 <strong>예외가 발생</strong>합니다. </p>



<p>예를 들어, 응답이 시작된 후 헤더나 상태 코드를 설정하려고 하면 예외가 발생합니다.</p>



<p><code>next.Invoke()</code> 호출 후 응답 본문에 쓰는 것은 다음과 같은 문제를 야기할 수 있습니다:</p>



<ul class="wp-block-list">
<li><strong>프로토콜 위반:</strong> 명시된 <code>Content-Length</code>보다 더 많은 내용을 작성하는 것과 같은 프로토콜 위반을 초래할 수 있습니다.</li>



<li><strong>본문 형식 손상:</strong> CSS 파일에 HTML 푸터(footer)를 작성하는 것처럼 본문 형식을 손상시킬 수 있습니다.</li>
</ul>



<p><code>HasStarted</code> 속성은 헤더가 전송되었는지 또는 본문이 작성되었는지 여부를 나타내는 유용한 힌트가 될 수 있습니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1fae0.png" alt="🫠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>app.Use</code> vs. <code>app.Run</code></h3>



<p>이 두 메서드는 파이프라인에 미들웨어를 추가하는 데 기본적이지만, 핵심적인 차이점이 있습니다:</p>



<p><code><strong>app.Use(async (context, next) => { ... })</strong></code></p>



<ul class="wp-block-list">
<li><strong>요청 수정 불가:</strong> <br>마지막 단계이므로 요청을 다음으로 전달하기 전에 수정할 수 없습니다.</li>



<li><strong>비종료(Non-Terminal) 미들웨어:</strong> <br>이 유형의 미들웨어는 일반적으로 어떤 작업을 수행한 다음, <code>next</code> 델리게이트를 호출하여 파이프라인의 다음 미들웨어로 제어를 전달합니다.</li>



<li><strong>요청/응답 수정 가능:</strong> <br>요청을 다음으로 전달하기 전에 요청이나 응답을 변경할 수 있습니다.</li>



<li><strong>예시:</strong> <br>인증, 로깅, 커스텀 헤더 추가 등.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>a<strong>pp.Run(async (context) => { ... })</strong></code></p>



<ul class="wp-block-list">
<li><strong>종료(Terminal) 미들웨어:</strong> <br>이 미들웨어는 <code>next</code>를 호출하지 않습니다. <br>파이프라인을 종료하고 <strong>자체적으로 응답을 생성</strong>합니다.</li>



<li><strong>최종 응답에 주로 사용:</strong> <br>더 이상 처리가 필요 없는 요청(예: 간단한 메시지 반환)을 처리하는 데 일반적으로 사용됩니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 여러 app.Run 호출의 결과

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello");
});

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again");
});

app.Run(); // 이 app.Run은 위의 app.Run들이 이미 파이프라인을 종료했기 때문에 절대 실행되지 않습니다.</pre>



<p>이 코드에서는 <strong>오직 첫 번째 <code>app.Run</code> 미들웨어만 실행됩니다.</strong> </p>



<p>&#8220;Hello&#8221;를 응답에 작성하여 파이프라인을 종료하고, 그 뒤의 <code>app.Run</code> (이것은 &#8220;Hello again&#8221;을 작성할 것임)은 실행될 기회를 얻지 못합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// app.Use와 app.Run으로 미들웨어 체인 연결하기

// 미들웨어 1
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello "); // 1. "Hello " 작성
    await next(context); // 2. 다음 미들웨어 호출
});

// 미들웨어 2
app.Use(async (context, next) => {
    await context.Response.WriteAsync("Hello again "); // 3. "Hello again " 작성
    await next(context); // 4. 다음 미들웨어 호출 (이 경우 app.Run)
});

// 미들웨어 3 (종료 미들웨어)
app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("Hello again"); // 5. "Hello again" 작성 후 파이프라인 종료
});</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>이 코드는 미들웨어를 올바르게 연결하는 방법을 보여줍니다.</p>



<ol start="1" class="wp-block-list">
<li>첫 번째 <code>app.Use</code>는 응답에 &#8220;Hello &#8220;를 작성하고 <code>next</code>를 호출하여 다음 미들웨어로 제어를 전달합니다.</li>



<li>두 번째 <code>app.Use</code>는 &#8220;Hello again &#8220;을 작성하고 역시 <code>next</code>를 호출합니다.</li>



<li>마지막 <code>app.Run</code> (종료 미들웨어)는 &#8220;Hello again&#8221;을 작성하고 파이프라인을 종료합니다.</li>
</ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2753.png" alt="❓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core의 커스텀 미들웨어</h3>



<p>ASP.NET Core는 다양한 내장 미들웨어 구성 요소를 제공하지만, 때로는 애플리케이션 고유의 특정 요구 사항을 해결하기 위해 자신만의 <strong>커스텀 미들웨어</strong>를 만들어야 할 수도 있습니다. </p>



<p>커스텀 미들웨어를 통해 다음을 수행할 수 있습니다:</p>



<ul class="wp-block-list">
<li><strong>로직 캡슐화:</strong> <br>관련 작업(예: 로깅, 보안 검사, 사용자 정의 헤더)을 재사용 가능한 구성 요소로 묶습니다.</li>



<li><strong>동작 사용자 정의:</strong> <br>애플리케이션의 요구 사항에 정확히 맞게 요청/응답 파이프라인을 조정합니다.</li>



<li><strong>코드 구성 개선:</strong> <br>미들웨어 코드를 깔끔하고 유지 관리하기 쉽게 만듭니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 미들웨어 클래스의 구조</h4>



<p><strong><code>IMiddleware</code> 구현:</strong> 이 인터페이스는 단 하나의 메서드 <code>InvokeAsync(HttpContext context, RequestDelegate next)</code>를 요구합니다. </p>



<p>이 메서드는 미들웨어 로직의 핵심입니다</p>



<p><code>InvokeAsync</code> 또는 <code>Invoke</code> 메서드</p>



<ul class="wp-block-list">
<li><strong><code>context</code>:</strong> <br><code>HttpContext</code>는 요청 및 응답 객체에 대한 접근을 제공합니다.</li>



<li><strong><code>next</code>:</strong> <br><code>RequestDelegate</code>는 파이프라인의 다음 미들웨어를 호출할 수 있도록 합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// MyCustomMiddleware.cs
namespace MiddlewareExample.CustomMiddleware
{
    public class MyCustomMiddleware : IMiddleware // IMiddleware 인터페이스 구현
    {
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            await context.Response.WriteAsync("My Custom Middleware - Starts\n"); // 1. 응답 시작 부분에 출력
            await next(context); // 2. 다음 미들웨어 호출
            await context.Response.WriteAsync("My Custom Middleware - Ends\n"); // 3. 다음 미들웨어 완료 후 응답 끝 부분에 출력
        }
    }

    // 쉽게 등록하기 위한 확장 메서드
    public static class CustomMiddlewareExtension
    {
        public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware&lt;MyCustomMiddleware>();
        }
    }
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// Program.cs (또는 Startup.cs)
using MiddlewareExample.CustomMiddleware;

// ... (다른 설정 코드) ...

builder.Services.AddTransient&lt;MyCustomMiddleware>(); // 트랜지언트 서비스로 등록

app.Use(async (HttpContext context, RequestDelegate next) => {
    await context.Response.WriteAsync("From Middleware 1\n"); // 첫 번째 미들웨어: 시작 부분
    await next(context);
});

app.UseMyCustomMiddleware(); // 확장 메서드를 사용하여 커스텀 미들웨어 추가

app.Run(async (HttpContext context) => {
    await context.Response.WriteAsync("From Middleware 3\n"); // 세 번째 미들웨어: 파이프라인 종료
});</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><strong>등록:</strong> <br>ASP.NET Core가 필요할 때 <code>MyCustomMiddleware</code> 인스턴스를 생성할 수 있도록 이를 트랜지언트 서비스로 등록합니다.</li>



<li><strong>파이프라인 통합:</strong> <br><code>app.UseMyCustomMiddleware()</code> 확장 메서드는 커스텀 미들웨어를 파이프라인에 추가합니다.</li>



<li><strong>실행 순서:</strong> <br>미들웨어 구성 요소는 파이프라인에 추가된 순서대로 실행됩니다. 이 경우 순서는 미들웨어 1, <code>MyCustomMiddleware</code>, 그리고 미들웨어 3이 됩니다.</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 커스텀 컨벤셔널 미들웨어 (Custom Conventional Middleware)</h3>



<p>ASP.NET Core 미들웨어에는 두 가지 유형이 있습니다: <strong>컨벤셔널(Conventional)</strong>과 <strong>팩토리 기반(Factory-based)</strong> </p>



<p>예시에서 보여진 컨벤셔널 미들웨어는 HTTP 요청 및 응답 처리를 위한 커스텀 로직을 캡슐화하는 간단하면서도 강력한 방법입니다.</p>



<p>주요 특징</p>



<ul class="wp-block-list">
<li><strong>클래스 기반:</strong> <br>컨벤셔널 미들웨어는 클래스로 구현됩니다.</li>



<li><strong>생성자 주입:</strong> <br>의존성(있는 경우)을 생성자를 통해 받습니다.</li>



<li><strong><code>Invoke</code> 메서드:</strong> <br>이 메서드는 각 요청을 처리하는 로직을 포함하는 미들웨어의 핵심입니다.</li>



<li><strong><code>RequestDelegate</code>:</strong> <br><code>Invoke</code> 메서드는 <code>RequestDelegate</code> 매개변수(<code>_next</code>로 명명)를 받습니다. 이 델리게이트는 파이프라인의 다음 미들웨어를 나타냅니다.</li>



<li><strong>유연성:</strong> <br><code>Invoke</code> 메서드 내에서 요청 및 응답 객체를 완벽하게 제어할 수 있습니다.</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="689" height="343" src="https://lycos7560.com/wp-content/uploads/2025/07/image-13.png" alt="" class="wp-image-40132" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-13.png 689w, https://lycos7560.com/wp-content/uploads/2025/07/image-13-300x149.png 300w" sizes="(max-width: 689px) 100vw, 689px" /></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// NameConcatenationMiddleware.cs
public class NameConcatenationMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Query.ContainsKey("firstname") &amp;&amp;
            context.Request.Query.ContainsKey("lastname"))
        {
            string fullName = $"{context.Request.Query["firstname"]} {context.Request.Query["lastname"]}";
            await context.Response.WriteAsync(fullName);
            return; // 명시적으로 반환하여 다음 미들웨어 호출 방지
        }
        
        await _next(context);
    }
}

// MiddlewareExtensions.cs
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseNameConcatenation(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware&lt;NameConcatenationMiddleware>();
    }
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f500.png" alt="🔀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 미들웨어 파이프라인의 이상적인 순서</h3>



<p>미들웨어의 순서는 애플리케이션의 동작과 효율성, 보안에 큰 영향을 미칩니다. </p>



<p>다음은 일반적으로 권장되는 이상적인 순서입니다</p>



<figure class="wp-block-image size-full"><img decoding="async" width="858" height="485" src="https://lycos7560.com/wp-content/uploads/2025/07/image-14.png" alt="" class="wp-image-40133" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-14.png 858w, https://lycos7560.com/wp-content/uploads/2025/07/image-14-300x170.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/image-14-768x434.png 768w" sizes="(max-width: 858px) 100vw, 858px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>예외/오류 처리(Exception/Error Handling):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 파이프라인의 어느 곳에서든 발생하는 예외를 Catch하고 처리합니다.</li>



<li><strong>예시:</strong> <code>UseExceptionHandler</code> (프로덕션용), <code>UseDeveloperExceptionPage</code> (개발 환경용).</li>



<li><strong>이유:</strong> 예외를 초기에 Catch하여 파이프라인 아래로 전파되어 더 큰 문제를 일으키는 것을 방지합니다.</li>
</ul>



<p><strong>HTTPS 리디렉션(HTTPS Redirection):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 보안을 위해 HTTP 요청을 HTTPS로 리디렉션합니다.</li>



<li><strong>예시:</strong> <code>UseHttpsRedirection</code>.</li>



<li><strong>이유:</strong> 보안을 최우선으로 하여 모든 통신이 암호화되도록 합니다.</li>
</ul>



<p><strong>정적 파일(Static Files):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 이미지, CSS, JavaScript 파일과 같은 정적 파일을 클라이언트에게 직접 제공합니다.</li>



<li><strong>예시:</strong> <code>UseStaticFiles</code>.</li>



<li><strong>이유:</strong> 정적 파일 요청은 빠르게 처리되어야 하며, 불필요하게 파이프라인의 다른 무거운 구성 요소를 거치지 않도록 일찍 처리합니다.</li>
</ul>



<p><strong>라우팅(Routing):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> URL을 기반으로 들어오는 요청을 특정 엔드포인트에 매칭합니다.</li>



<li><strong>예시:</strong> <code>UseRouting</code>, <code>UseEndpoints</code>.</li>



<li><strong>이유:</strong> 라우팅은 애플리케이션의 핵심 로직이 요청을 어떻게 처리할지 결정하는 기반이 됩니다.</li>
</ul>



<p><strong>CORS (Cross-Origin Resource Sharing):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 다른 도메인으로부터의 안전한 교차 출처(cross-origin) 요청을 가능하게 합니다.</li>



<li><strong>예시:</strong> <code>UseCors</code>.</li>



<li><strong>이유:</strong> 인증/인가 전에 위치하여, 사전 요청(preflight request)이 불필요하게 인증/인가 미들웨어를 거치지 않도록 합니다.</li>
</ul>



<p><strong>인증(Authentication):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 사용자 신원을 확인하고 사용자 주체(principal)를 설정합니다.</li>



<li><strong>예시:</strong> <code>UseAuthentication</code>.</li>



<li><strong>이유:</strong> 사용자가 누구인지 확인한 후에 리소스에 대한 접근 권한을 부여할 수 있습니다.</li>
</ul>



<p><strong>인가(Authorization):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 사용자가 특정 리소스에 접근하거나 특정 작업을 수행할 수 있는지 여부를 결정합니다.</li>



<li><strong>예시:</strong> <code>UseAuthorization</code>.</li>



<li><strong>이유:</strong> 인증된 사용자에게만 권한 부여 여부를 검사합니다.</li>
</ul>



<p><strong>커스텀 미들웨어(Custom Middleware):</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 로깅, 기능 플래그 등 애플리케이션별 미들웨어 구성 요소를 처리합니다.</li>



<li><strong>이유:</strong> 애플리케이션별 로직을 적절한 단계에서 파이프라인 내에 배치합니다.</li>
</ul>



<p><strong>MVC/Razor Pages/Minimal APIs:</strong></p>



<ul class="wp-block-list">
<li><strong>목적:</strong> 실제 애플리케이션의 최종 엔드포인트 처리 로직을 실행합니다.</li>



<li><strong>예시:</strong> <code>MapControllers()</code>, <code>MapRazorPages()</code>, <code>MapGet()</code> 등.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="848" height="589" src="https://lycos7560.com/wp-content/uploads/2025/07/image-15.png" alt="" class="wp-image-40134" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-15.png 848w, https://lycos7560.com/wp-content/uploads/2025/07/image-15-300x208.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/image-15-768x533.png 768w" sizes="(max-width: 848px) 100vw, 848px" /><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0</a></figcaption></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>Program.cs</code> 파일에 미들웨어 구성 요소가 추가되는 순서는 요청 시 미들웨어 구성 요소가 호출되는 순서를 정의하며, 응답 시에는 역순으로 호출됩니다. </p>



<p>이러한 <strong>순서는 보안, 성능 및 기능에 매우 중요합니다.</strong></p>



<p><code>Program.cs</code>의 다음 강조 표시된 코드는 보안 관련 미들웨어 구성 요소를 일반적으로 권장되는 순서로 추가하는 예시입니다:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="19-43" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext&lt;ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity&lt;IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores&lt;ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> UseWhen()</h3>



<p><code>UseWhen()</code>은 ASP.NET Core의 <code>IApplicationBuilder</code> 인터페이스에 있는 강력한 확장 메서드입니다. </p>



<p>이는 <strong>조건(predicate)에 따라 미들웨어를 요청 파이프라인에 조건부로 추가</strong>할 수 있도록 합니다. </p>



<p>즉, 특정 조건이 충족될 때만 특정 미들웨어 구성 요소가 실행되는 동적인 파이프라인을 만들 수 있습니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseWhen(
    context => /* 여기에 조건 */, // HttpContext를 받아 true/false 반환
    app => /* 이 분기에서 실행될 미들웨어 구성 */ // 조건이 true일 때 실행될 미들웨어 파이프라인
);</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><strong><code>context</code>:</strong> <br>현재 요청을 나타내는 <code>HttpContext</code> 객체입니다.</li>



<li><strong>Predicate (조건):</strong> <br><code>HttpContext</code>를 받아들이고 미들웨어 분기가 실행되어야 할 경우 <code>true</code>를, 그렇지 않을 경우 <code>false</code>를 반환하는 함수입니다.</li>



<li><strong>Middleware Configuration (미들웨어 구성):</strong> <br>조건이 <code>true</code>일 때 실행되어야 할 미들웨어 구성 요소를 구성하는 액션입니다. <br>여기서 <code>app.Use()</code>, <code>app.Run()</code>, 또는 다른 미들웨어 등록 메서드를 사용합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>UseWhen()</code> 작동 방식</h4>



<ul class="wp-block-list">
<li><strong>조건 평가:</strong> <br>요청이 들어오면 <code>UseWhen()</code> 메서드는 먼저 <code>HttpContext</code>에 대해 조건 함수를 평가합니다.</li>



<li><strong>분기(조건이 true일 경우):</strong> <br>조건이 <code>true</code>를 반환하면, 구성 액션에 지정된 미들웨어 분기가 실행됩니다. <br>요청은 이 분기를 통해 흐르며, 수정되거나 응답을 생성할 수 있습니다.</li>



<li><strong>메인 파이프라인 재진입:</strong> <br>분기가 실행된 후(또는 조건이 <code>false</code>여서 건너뛰어진 경우), 요청 흐름은 메인 파이프라인으로 다시 진입하여 <code>UseWhen()</code> 호출 뒤에 등록된 다음 미들웨어 구성 요소로 계속 진행됩니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.UseWhen(
    context => context.Request.Query.ContainsKey("username"), // 조건: 쿼리 문자열에 "username"이 있는지 확인
    app => {
        app.Use(async (context, next) =>
        {
            await context.Response.WriteAsync("Hello from Middleware branch\n"); // 분기 미들웨어: "Hello from Middleware branch" 작성
            await next(); // 다음 미들웨어 호출
        });
    });

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from middleware at main chain"); // 메인 파이프라인 미들웨어
});</pre>



<ul class="wp-block-list">
<li><strong>조건:</strong> <br><code>context.Request.Query.ContainsKey("username")</code> 조건은 쿼리 문자열에 &#8220;username&#8221;이라는 매개변수가 포함되어 있는지 확인합니다.</li>



<li><strong>분기 미들웨어:</strong> <br>&#8220;username&#8221; 매개변수가 존재하면 분기 미들웨어가 실행됩니다. <br>이 미들웨어는 응답에 &#8220;Hello from Middleware branch&#8221;를 작성하고 <code>next</code>를 호출하여 나머지 파이프라인이 계속되도록 합니다.</li>



<li><strong>메인 파이프라인:</strong> <br>마지막 <code>app.Run</code> 미들웨어는 메인 파이프라인의 일부입니다. <br>이는 응답에 &#8220;Hello from middleware at main chain&#8221;을 작성합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f98b.png" alt="🦋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <code>UseWhen()</code> 사용 시점</h4>



<ul class="wp-block-list">
<li><strong>조건부 기능:</strong> <br>요청에 따라 특정 기능을 활성화하거나 비활성화합니다 (예: 특정 사용자에게만 로깅, 쿼리 매개변수에 따른 캐싱 규칙 적용).</li>



<li><strong>동적 파이프라인:</strong> <br>다양한 요청에 맞춰 조정되는 파이프라인을 만듭니다 (예: 특정 경로에 대해 다른 인증 미들웨어).</li>



<li><strong>A/B 테스트:</strong> <br>실험을 위해 사용자 하위 집합을 대체 미들웨어 분기를 통해 라우팅합니다.</li>



<li><strong>디버깅 및 진단:</strong> <br>개발 환경에서만 진단 미들웨어를 적용합니다.</li>
</ul>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/">ASP.NET Core의 미들웨어(Middleware)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net-core%ec%9d%98-%eb%af%b8%eb%93%a4%ec%9b%a8%ec%96%b4middleware/40130/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
