개요 이 예제에서는 이전 예제에서 다룬 로그인 프로세스에 추가적으로 Consent 확인 단계를 포함시킵니다. Consent 페이지는 외부 클라이언트가 우리 권한 중앙 서버의 승인을 필요로 하는 상황에 적합합니다. 본 예제에서는 여전히 암묵적(Implicit) 인증 모드를 사용하며, 보호된 MVC 클라이언트 페이지에 접근할 때 로그인 페이지로 리다이렉션되고 이후 Consent 페이지로 다시 전환됩니다.
AuthServer 측 Consent 컨트롤러 및 뷰 추가 2.1 Consent 컨트롤러 및 뷰 구성 아래는 Consent 뷰의 핵심 코드입니다.
@model ConsentViewModel
<div class="page-consent">
<div class="lead">
@if (Model.ClientLogoUrl != null)
{
<div class="client-logo"></div>
}
<h1>@Model.ClientName<small class="text-muted">의 요청 승인</small></h1>
<p>허용하지 않는 권한은 선택 해제하세요.</p>
</div>
<form method="post" asp-action="Index">
<input type="hidden" asp-for="ReturnUrl" />
<div class="row">
<div class="col-sm-8">
@if (Model.IdentityScopes.Any())
{
<div class="card">
<div class="card-header">개인 정보</div>
@foreach (var scope in Model.IdentityScopes) { <partial model="scope" name="_ScopeListItem"></partial> }
</div>
}
@if (Model.ApiScopes.Any())
{
<div class="card">
<div class="card-header">애플리케이션 접근 권한</div>
@foreach (var scope in Model.ApiScopes) { <partial model="scope" name="_ScopeListItem"></partial> }
</div>
}
<div class="card">
<div class="card-header">설명</div>
<div class="card-body">
<input class="form-control" placeholder="장치 설명 또는 이름" asp-for="Description" autofocus />
</div>
</div>
@if (Model.AllowRememberConsent)
{
<div class="form-check">
<input class="form-check-input" asp-for="RememberConsent" />
<label class="form-check-label" asp-for="RememberConsent">결정 기억하기</label>
</div>
}
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button type="submit" name="button" value="yes" class="btn btn-primary">승인</button>
<button type="submit" name="button" value="no" class="btn btn-secondary">거절</button>
</div>
</div>
</form>
</div>
컨트롤러 POST 처리 로직 아래는 Consent 입력 데이터를 처리하는 컨트롤러 메서드입니다.
private async Task<ProcessConsentResult> HandleConsent(ConsentInputModel input)
{
var result = new ProcessConsentResult();
var request = await _interaction.GetAuthorizationContextAsync(input.ReturnUrl);
if (request == null) return result;
ConsentResponse grantedConsent = null;
if (input.Button == "no")
{
grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
}
else if (input.Button == "yes")
{
if (input.ScopesConsented?.Any() == true)
{
var scopes = input.ScopesConsented;
if (!ConsentOptions.EnableOfflineAccess)
{
scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
}
grantedConsent = new ConsentResponse
{
RememberConsent = input.RememberConsent,
ScopesValuesConsented = scopes.ToArray(),
Description = input.Description
};
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent));
}
else
{
result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
}
}
else
{
result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
}
if (grantedConsent != null)
{
await _interaction.GrantConsentAsync(request, grantedConsent);
result.RedirectUri = input.ReturnUrl;
result.Client = request.Client;
}
else
{
result.ViewModel = await BuildViewModelAsync(input.ReturnUrl, input);
}
return result;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ConsentInputModel input)
{
var result = await HandleConsent(input);
if (result.IsRedirect)
{
return Redirect(result.RedirectUri);
}
if (result.HasValidationError)
{
ModelState.AddModelError(string.Empty, result.ValidationError);
}
if (result.ShowView)
{
return View("Index", result.ViewModel);
}
return View("Error");
}
테스트
localhost:5010에 접속하면 로그인 페이지로 리다이렉션됩니다.localhost:5030에서 성공적으로 로그인한 후, 정상적인 권한 부여가 이루어져 홈 페이지로 리다이렉션됩니다.
질문
- Consent 동의 이후 내부 처리 절차는 무엇인가?
- Consent 거절 시 클라이언트 측에서 어떻게 처리되는가?