📚 핵심 정리
인증과 권한 관리
ASP.NET Core Identity와 AuthorizeView를 사용해 로그인·권한 제어를 구현합니다.
📌 학습 목표
- ASP.NET Core Identity의 핵심 클래스(
UserManager,SignInManager,RoleManager)의 역할을 이해합니다. - Program.cs에서 인증·권한 미들웨어를 올바른 순서로 등록하고 설정할 수 있습니다.
CascadingAuthenticationState와AuthorizeView로 인증 상태 기반 UI를 선언적으로 구성할 수 있습니다.[Authorize]속성으로 페이지 전체를 역할(Role) 및 정책(Policy) 기반으로 보호할 수 있습니다.- 클레임(Claim) 기반 사용자 정의 정책을 정의하고 세밀한 접근 제어를 구현할 수 있습니다.
📝 개념 설명
1. Blazor 인증 아키텍처 개요
Blazor 앱의 인증 방식은 호스팅 모델에 따라 달라집니다.
- Blazor Server: 서버가 SignalR 연결을 유지하며 인증 상태를 관리합니다. ASP.NET Core 미들웨어 파이프라인의 쿠키 기반 인증을 그대로 활용합니다.
- Blazor WebAssembly: 브라우저에서 실행되므로 JWT 토큰 또는 외부 OpenID Connect 공급자를 통해 인증합니다. 서버 API와 별도 인증 흐름이 필요합니다.
💡 이 글은 Blazor Server 환경에서 ASP.NET Core Identity를 이용한 쿠키 기반 인증을 중심으로 설명합니다.
🔄 Blazor Server 인증 흐름
로그인 요청
→
SignInManager 검증
→
인증 쿠키 발급
→
AuthenticationState 갱신
→
컴포넌트 조건부 렌더링
2. ASP.NET Core Identity 핵심 클래스
ASP.NET Core Identity는 사용자 인증·인가를 위한 종합 멤버십 프레임워크입니다.
- UserManager<TUser>: 사용자 CRUD, 비밀번호 해싱·검증, 클레임·역할 할당을 담당합니다.
- SignInManager<TUser>: 로그인·로그아웃, 2단계 인증, 계정 잠금 처리를 담당합니다.
- RoleManager<TRole>: 역할(Role) 생성·삭제·조회를 담당합니다.
필요한 NuGet 패키지
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
3. Program.cs — 서비스 등록 및 미들웨어 설정
// ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
public string? DisplayName { get; set; }
}
// ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
}
// Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Lockout.MaxFailedAccessAttempts = 5;
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("PremiumUser", policy => policy.RequireClaim("Subscription", "Premium"));
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
var app = builder.Build();
app.UseAuthentication(); // 반드시 UseAuthorization() 앞에 위치
app.UseAuthorization();
app.MapRazorPages();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
⚠️
UseAuthentication()은 UseAuthorization()보다 반드시 먼저 호출해야 합니다. 순서가 바뀌면 인증 상태가 권한 검사에 반영되지 않습니다.4. CascadingAuthenticationState — 인증 상태 전파
모든 하위 컴포넌트에 인증 상태를 전달하려면 App.razor에서 전체 라우터를 <CascadingAuthenticationState>로 감싸야 합니다.
<!-- App.razor -->
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<p>이 페이지에 접근할 권한이 없습니다.</p>
<a href="/login">로그인하기</a>
</NotAuthorized>
<Authorizing>
<p>인증 확인 중...</p>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound><p>페이지를 찾을 수 없습니다.</p></NotFound>
</Router>
</CascadingAuthenticationState>
5. AuthorizeView 컴포넌트 — 선언적 UI 제어
<AuthorizeView>는 인증 상태에 따라 다른 UI를 렌더링하는 Blazor 전용 컴포넌트입니다. 세 가지 자식 슬롯(Authorized, NotAuthorized, Authorizing)을 제공합니다.
<!-- 로그인 여부에 따른 기본 분기 -->
<AuthorizeView>
<Authorized>
<p>환영합니다, @context.User.Identity?.Name 님!</p>
<a href="/profile">내 프로필</a>
</Authorized>
<NotAuthorized>
<a href="/login">로그인</a> | <a href="/register">회원가입</a>
</NotAuthorized>
<Authorizing>
<span>인증 상태 확인 중...</span>
</Authorizing>
</AuthorizeView>
<!-- 역할 기반 분기 -->
<AuthorizeView Roles="Admin">
<Authorized><a href="/admin">관리자 패널</a></Authorized>
</AuthorizeView>
<!-- 정책 기반 분기 -->
<AuthorizeView Policy="PremiumUser">
<Authorized>
<p>프리미엄 전용 콘텐츠입니다.</p>
</Authorized>
<NotAuthorized>
<p>프리미엄 회원 전용입니다. 업그레이드하세요.</p>
</NotAuthorized>
</AuthorizeView>
6. [Authorize] 속성 — 페이지 전체 보호
@attribute [Authorize]를 사용하면 인증되지 않은 사용자가 해당 페이지에 접근할 때 App.razor의 <NotAuthorized> 슬롯이 렌더링됩니다.
@* 로그인한 모든 사용자 허용 *@
@page "/dashboard"
@attribute [Authorize]
@* Admin 역할 전용 *@
@page "/admin"
@attribute [Authorize(Roles = "Admin")]
@* 정책 기반 접근 제어 *@
@page "/premium"
@attribute [Authorize(Policy = "PremiumUser")]
7. 클레임(Claim) 기반 권한 제어
클레임은 사용자에 대한 키-값 형태의 속성 정보입니다. 역할보다 세밀한 권한 제어가 필요할 때 사용합니다.
// 사용자에게 클레임 추가
var claims = new List<Claim>
{
new Claim("Subscription", "Premium"),
new Claim("Department", "Engineering"),
new Claim("Level", "Senior")
};
await _userManager.AddClaimsAsync(user, claims);
// Program.cs — 복합 클레임 정책 정의
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("SeniorEngineer", policy =>
{
policy.RequireClaim("Department", "Engineering");
policy.RequireClaim("Level", "Senior");
});
});
⚖️ AuthorizeView vs [Authorize] 속성 비교
| 항목 | AuthorizeView | [Authorize] 속성 |
|---|---|---|
| 적용 범위 | 컴포넌트 내 일부 UI 영역 | 페이지·컴포넌트 전체 |
| 사용 위치 | Razor 마크업 내부 | @attribute 지시문 |
| 미인증 시 처리 | NotAuthorized 슬롯 렌더링 | App.razor NotAuthorized로 전환 |
| 역할 지정 | Roles=”Admin” | [Authorize(Roles=”Admin”)] |
| 정책 지정 | Policy=”PolicyName” | [Authorize(Policy=”PolicyName”)] |
| 사용자 정보 접근 | @context.User 직접 사용 | 코드에서 별도 주입 필요 |
💡 예제 & 실습
실습 1: 로그인 컴포넌트 구현
@page "/login"
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager
@inject NavigationManager NavigationManager
<h2>로그인</h2>
<EditForm Model="@loginModel" OnValidSubmit="HandleLogin">
<DataAnnotationsValidator />
<div>
<label>이메일</label>
<InputText @bind-Value="loginModel.Email" />
</div>
<div>
<label>비밀번호</label>
<InputText type="password" @bind-Value="loginModel.Password" />
</div>
<button type="submit">로그인</button>
</EditForm>
@if (!string.IsNullOrEmpty(errorMessage))
{ <p style="color:red">@errorMessage</p> }
@code {
private LoginModel loginModel = new();
private string errorMessage = string.Empty;
private async Task HandleLogin()
{
var user = await UserManager.FindByEmailAsync(loginModel.Email);
if (user == null) { errorMessage = "이메일 또는 비밀번호가 올바르지 않습니다."; return; }
var result = await SignInManager.PasswordSignInAsync(
user, loginModel.Password, isPersistent: false, lockoutOnFailure: true);
if (result.Succeeded)
NavigationManager.NavigateTo("/", forceLoad: true); // 쿠키 반영을 위해 forceLoad 필수
else if (result.IsLockedOut)
errorMessage = "계정이 잠겨 있습니다. 잠시 후 다시 시도하세요.";
else
errorMessage = "이메일 또는 비밀번호가 올바르지 않습니다.";
}
public class LoginModel
{
[Required][EmailAddress] public string Email { get; set; } = string.Empty;
[Required] public string Password { get; set; } = string.Empty;
}
}
실습 2: 회원가입 및 역할 부여
@page "/register"
@inject UserManager<ApplicationUser> UserManager
@inject RoleManager<IdentityRole> RoleManager
@inject NavigationManager NavigationManager
@code {
private List<string> errors = new();
private async Task HandleRegister()
{
// 역할이 없으면 먼저 생성
if (!await RoleManager.RoleExistsAsync("User"))
await RoleManager.CreateAsync(new IdentityRole("User"));
var user = new ApplicationUser { UserName = registerModel.Email, Email = registerModel.Email };
var result = await UserManager.CreateAsync(user, registerModel.Password);
if (result.Succeeded)
{
await UserManager.AddToRoleAsync(user, "User"); // 기본 역할 부여
NavigationManager.NavigateTo("/login");
}
else
errors = result.Errors.Select(e => e.Description).ToList();
}
}
실습 3: @code 블록에서 인증 상태 직접 확인
@inject AuthenticationStateProvider AuthStateProvider
@code {
private string? currentUserName;
private bool isAdmin;
protected override async Task OnInitializedAsync()
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity?.IsAuthenticated == true)
{
currentUserName = user.Identity.Name;
isAdmin = user.IsInRole("Admin");
string? sub = user.FindFirst("Subscription")?.Value;
}
}
}
실습 4: 로그아웃 처리
@inject SignInManager<ApplicationUser> SignInManager
@inject NavigationManager NavigationManager
<button @onclick="HandleLogout">로그아웃</button>
@code {
private async Task HandleLogout()
{
await SignInManager.SignOutAsync();
NavigationManager.NavigateTo("/", forceLoad: true);
}
}
실습 5: 앱 시작 시 역할 시드(Seed)
역할은 데이터베이스에 레코드로 존재해야 합니다. 앱 시작 시 한 번 생성하는 패턴을 사용합니다.
// Program.cs — app.Run() 이전에 삽입
using (var scope = app.Services.CreateScope())
{
var roleManager = scope.ServiceProvider
.GetRequiredService<RoleManager<IdentityRole>>();
foreach (var role in new[] { "Admin", "User", "Premium" })
{
if (!await roleManager.RoleExistsAsync(role))
await roleManager.CreateAsync(new IdentityRole(role));
}
}
app.Run();
⚠️ 자주 틀리는 것 / 주의사항
① forceLoad 없이 페이지 이동
문제: 로그인 후 메인 페이지로 이동했는데 여전히 비로그인 상태로 보입니다.
원인: Blazor Server는 SignalR 위에서 동작하므로 쿠키 기반 인증은 HTTP 요청 시점에 처리됩니다.
해결:
원인: Blazor Server는 SignalR 위에서 동작하므로 쿠키 기반 인증은 HTTP 요청 시점에 처리됩니다.
NavigateTo(path)만으로는 인증 쿠키가 갱신되지 않습니다.해결:
NavigationManager.NavigateTo("/", forceLoad: true)로 전체 페이지를 새로 로드합니다. 로그아웃 시에도 동일하게 적용합니다.
② UseAuthentication · UseAuthorization 순서 오류
문제: 로그인 상태인데
해결:
[Authorize] 페이지에서 권한 없음 오류가 발생합니다.해결:
Program.cs에서 미들웨어 순서를 반드시 지킵니다.app.UseAuthentication(); → 사용자가 누구인지 먼저 확인app.UseAuthorization(); → 그 다음 무엇을 할 수 있는지 결정
③ CascadingAuthenticationState 또는 AuthorizeRouteView 누락
문제:
원인:
해결: App.razor 구조를 이 글의 실습 예시와 동일하게 수정합니다.
<AuthorizeView>가 항상 <NotAuthorized> 슬롯을 렌더링합니다.원인:
App.razor에서 <CascadingAuthenticationState>로 감싸지 않았거나, <RouteView>를 <AuthorizeRouteView>로 교체하지 않았습니다.해결: App.razor 구조를 이 글의 실습 예시와 동일하게 수정합니다.
④ 클라이언트 측 권한 체크만으로 보안 완성 착각
주의:
AuthorizeView와 [Authorize]는 UI 렌더링만 제어합니다. Blazor WebAssembly에서는 클라이언트 코드를 조작할 수 있으므로, 민감한 데이터를 다루는 서버 API 엔드포인트에서도 반드시 독립적인 권한 검사를 수행해야 합니다.
⑤ 역할(Role)을 DB에 먼저 생성하지 않음
문제:
원인: Identity 역할은 데이터베이스 레코드로 존재해야 합니다. 역할 생성 없이 사용자에게 부여할 수 없습니다.
해결: 실습 5의 시드 코드를 참고해 앱 시작 시 기본 역할을 생성합니다.
[Authorize(Roles = "Admin")]을 설정했지만 관리자도 접근이 차단됩니다.원인: Identity 역할은 데이터베이스 레코드로 존재해야 합니다. 역할 생성 없이 사용자에게 부여할 수 없습니다.
해결: 실습 5의 시드 코드를 참고해 앱 시작 시 기본 역할을 생성합니다.
🎯 마무리
ASP.NET Core Identity와 Blazor의 AuthorizeView·[Authorize]를 조합하면, 로그인부터 역할·클레임 기반의 세밀한 접근 제어까지 선언적이고 안전하게 구현할 수 있습니다. 미들웨어 등록 순서와 forceLoad, 서버 측 검증의 중요성을 반드시 기억하십시오.
✅ 핵심 정리
- ASP.NET Core Identity: UserManager(사용자 관리), SignInManager(로그인 처리), RoleManager(역할 관리) 세 클래스가 핵심입니다.
- 미들웨어 순서:
UseAuthentication()→UseAuthorization()순서를 반드시 지킵니다. - CascadingAuthenticationState: App.razor 최상위에 배치해야 하위 컴포넌트 전체에 인증 상태가 전파됩니다.
- AuthorizeView: UI 일부를 인증 상태별로 분기합니다.
@context.User로 사용자 정보에 직접 접근 가능합니다. - [Authorize] 속성: 페이지 전체를 보호하며 Roles·Policy 파라미터로 대상을 세분화합니다.
- forceLoad: true: 로그인·로그아웃 후 페이지 이동 시 반드시 사용해 인증 쿠키를 즉시 반영합니다.
- 역할 시드 필수: 역할은 DB에 먼저 존재해야 합니다. 앱 시작 시
RoleManager.CreateAsync()로 기본 역할을 생성합니다. - 서버 측 검증 필수: 클라이언트 UI 제어만으로는 보안이 완전하지 않으므로 API 엔드포인트에서도 인증·인가를 수행합니다.