.NET Core는 유연한 권한 부여를 위해 정책 기반 권한 부여라는 새로운 방식을 도입했습니다. 이는 권한 부여 시스템의 핵심입니다. 정책 기반 권한 부여를 사용하려면 먼저 권한 부여 정책을 정의해야 합니다. 이러한 정책은 본질적으로 Claim에 대한 일련의 어설션입니다. 역할 기반 권한 부여 및 스키마 기반 권한 부여는 편의를 위한 구문일 뿐이며, 궁극적으로 모두 권한 부여 정책을 생성합니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
// 간단한 역할 기반 정책 예시
options.AddPolicy("AdminOnly", policy => policy.RequireRole("administrator"));
// Claim 기반 정책 예시
options.AddPolicy("SpecificUser", policy => policy.RequireClaim(ClaimTypes.Role, "administrator"));
// 특정 Claim 값 집합을 요구하는 정책 예시
// options.AddPolicy("FounderAccess", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
[Authorize(Policy = "AdminOnly")]
public ActionResult
사용자 정의 정책 권한 부여
정책 기반 권한 부여에서 중요한 개념은 Requirements입니다. 각 Requirement는 단일 권한 부여 조건을 나타냅니다. Requirement는 IAuthorizationRequirement 인터페이스를 상속해야 합니다. ASP.NET Core는 이미 몇 가지 일반적인 구현을 내장하고 있습니다.
AssertionRequirement: 가장 기본적인 어설션을 사용하여 권한 부여 정책을 선언합니다.DenyAnonymousAuthorizationRequirement: 익명 사용자의 액세스를 금지하는 정책을 나타내며,AuthorizationOptions에서 기본 정책으로 설정됩니다.ClaimsAuthorizationRequirement: 예상되는 Claim이 Claim 집합에 포함되는지 확인하는 정책을 나타냅니다.RolesAuthorizationRequirement:ClaimsPrincipal.IsInRole을 사용하여 예상되는 역할을 포함하는지 확인하는 정책을 나타냅니다.NameAuthorizationRequirement:ClaimsPrincipal.Identities.Name을 사용하여 예상되는 이름을 포함하는지 확인하는 정책을 나타냅니다.OperationAuthorizationRequirement: 작업 기반 권한 부여 정책을 나타냅니다.
OperationAuthorizationRequirement를 제외한 대부분의 요구 사항에는 RequireClaim, RequireRole, RequireUserName과 같은 편리한 추가 메서드가 있습니다.
기본 제공 Requirement가 요구 사항을 충족하지 못하면 사용자 지정 Requirement를 정의할 수 있습니다.
사용자 지정 정책 권한 부여 시연
Startup 클래스 구성
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
// 사용자 지정 권한 부여 요구 사항 추가
options.AddPolicy("CustomPermission", policy => policy.Requirements.Add(new PermissionRequirement()));
}).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
// 로그인 경로 설정
options.LoginPath = new PathString("/api/access/login");
});
services.AddTransient<IUserService, UserService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// 사용자 지정 권한 부여 처리기 등록
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddControllers();
}
Configure 메서드에서 추가
app.UseAuthentication(); // 인증 미들웨어 사용
app.UseAuthorization(); // 권한 부여 미들웨어 사용
PermissionRequirement 클래스 생성
// 사용자 지정 권한 부여 요구 사항 정의
public class PermissionRequirement : IAuthorizationRequirement
{
}
PermissionHandler 클래스 생성
// 사용자 지정 권한 부여 요구 사항 처리기
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserService _userService;
private readonly IHttpContextAccessor _accessor;
public PermissionHandler(IUserService userService, IHttpContextAccessor accessor)
{
_userService = userService;
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var httpContext = _accessor.HttpContext;
var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
Guid userId;
// Claim에서 사용자 ID 추출 시도
if (!Guid.TryParse(httpContext.User.Claims.FirstOrDefault(c => c.Type == "userId")?.Value, out userId))
{
return Task.CompletedTask; // 사용자 ID를 가져오지 못하면 처리 중단
}
var allowedUrls = _userService.GetUserAllowedUrls(userId); // 사용자가 접근할 수 있는 URL 목록 가져오기
var requestedUrl = httpContext.Request.Path.Value?.ToLowerInvariant(); // 요청된 URL 가져오기
// 사용자가 접근할 수 있는 URL 목록에 요청된 URL이 포함되어 있는지 확인
if (allowedUrls != null && allowedUrls.Contains(requestedUrl))
{
context.Succeed(requirement); // 권한 부여 성공
}
}
return Task.CompletedTask; // 권한 부여 실패 또는 비인증 사용자
}
}
테스트 사용자 데이터
실제 애플리케이션에서는 이 데이터를 데이터베이스에서 읽어와야 합니다. 다음은 예시 데이터 구조입니다.
public static class TestUserData
{
public static List<ApplicationUser> Users = new List<ApplicationUser>
{
new ApplicationUser { Id = Guid.NewGuid(), UserName = "Alice", Password = "password123", Roles = new List<string>{ "admin", "api_user" }, AllowedUrls = new List<string>{ "/api/data/admin", "/api/data/public" }},
new ApplicationUser { Id = Guid.NewGuid(), UserName = "Bob", Password = "password456", Roles = new List<string>{ "api_user" }, AllowedUrls = new List<string>{ "/api/data/public" }},
new ApplicationUser { Id = Guid.NewGuid(), UserName = "Charlie", Password = "password789", Roles = new List<string>{ "admin" }, AllowedUrls = new List<string>{ "/api/data/admin" }}
};
}
public class ApplicationUser
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public List<string> Roles { get; set; }
public List<string> AllowedUrls { get; set; }
}
UserService 구현
public interface IUserService
{
List<string> GetUserAllowedUrls(Guid userId);
}
public class UserService : IUserService
{
public List<string> GetUserAllowedUrls(Guid userId)
{
var user = TestUserData.Users.SingleOrDefault(u => u.Id.Equals(userId));
return user?.AllowedUrls;
}
}
API 컨트롤러
[Authorize(Policy = "CustomPermission")] // 사용자 지정 정책 적용
[Route("api/[controller]")]
[ApiController]
public class DataController : ControllerBase
{
[HttpGet("[action]")]
public ActionResult<IEnumerable<string>> GetAdminData()
{
return new string[] { "Protected Admin Data" };
}
[HttpGet("[action]")]
public ActionResult<IEnumerable<string>> GetPublicData()
{
return new string[] { "Publicly Accessible Data" };
}
[AllowAnonymous] // 익명 사용자 허용
[HttpPost("[action]")]
public async Task<ActionResult> Login([FromBody]LoginRequest request)
{
var user = TestUserData.Users.FirstOrDefault(u => u.UserName == request.Username && u.Password == request.Password);
if (user != null)
{
// 사용자 식별 정보 생성 (Claim)
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("userId", user.Id.ToString())); // 사용자 ID Claim 추가
// 쿠키 인증을 사용하여 로그인 처리
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
if (!string.IsNullOrEmpty(request.ReturnUrl))
{
return Redirect(request.ReturnUrl); // 리디렉션 URL이 있으면 해당 URL로 이동
}
else
{
return RedirectToAction(nameof(DataController.GetPublicData), "Data"); // 기본적으로 공개 데이터로 이동
}
}
else
{
return BadRequest("Invalid username or password."); // 잘못된 사용자 이름 또는 비밀번호
}
}
public class LoginRequest
{
public string Username { get; set; }
public string Password { get; set; }
public string ReturnUrl { get; set; }
}
}
이 구성을 사용하면 'Alice' 사용자는 모든 API 엔드포인트를 접근할 수 있고, 'Bob' 사용자는 'GetPublicData'만 접근할 수 있으며, 'Charlie' 사용자는 'GetAdminData'만 접근할 수 있습니다.