5. 使用IdentityServer4 实现 基于OIDC 的内部MVC客户端认证 - 实现Consent

1. 概述

  • 本例在上一个示例的基础上,为客户端登录过程增加Consent 确认过程.

  • Consent 页面适合外部客户端登录时需要我们授权中心授权的场景。

  • 本示例继续使用上一个示例中隐式授权码(Implicit)模式,当用户访问受保护的客户端页面时,MVC客户端会重定向到登录页,输入用户名密码后再重定向到Conset 页面。

2. AuthServer 端增加Consent控制器和视图

View 核心代码:

@model ConsentViewModel

<div class="page-consent">
    <div class="lead">
<!-- client logo-->
        @if (Model.ClientLogoUrl != null)
        {

            <div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
        }
        <h1>
            @Model.ClientName
            <small class="text-muted">is requesting your permission</small>
        </h1>
        <p>Uncheck the permissions you do not wish to grant.</p>
    </div>

    <div class="row">
        <div class="col-sm-8">
        </div>
    </div>

    <form asp-action="Index">
        <input type="hidden" asp-for="ReturnUrl" />
        <div class="row">
            <div class="col-sm-8">
     <!-- client scopes-->
                @if (Model.IdentityScopes.Any())
                {
                    <div class="form-group">
                        <div class="card">
                            <div class="card-header">
                                <span class="glyphicon glyphicon-user"></span>
                                Personal Information
                            </div>
                            <ul class="list-group list-group-flush">
                                @foreach (var scope in Model.IdentityScopes)
                                {
                                    <partial name="_ScopeListItem" model="@scope" />
                                }
                            </ul>
                        </div>
                    </div>
                }

                @if (Model.ApiScopes.Any())
                {
                    <div class="form-group">
                        <div class="card">
                            <div class="card-header">
                                <span class="glyphicon glyphicon-tasks"></span>
                                Application Access
                            </div>
                            <ul class="list-group list-group-flush">
                                @foreach (var scope in Model.ApiScopes)
                                {
                                    <partial name="_ScopeListItem" model="scope" />
                                }
                            </ul>
                        </div>
                    </div>
                }

                <div class="form-group">
                    <div class="card">
                        <div class="card-header">
                            <span class="glyphicon glyphicon-tasks"></span>
                            Description
                        </div>
                        <div class="card-body">
                            <input class="form-control" placeholder="Description or name of device" asp-for="Description" autofocus>
                        </div>
                    </div>
                </div>

                @if (Model.AllowRememberConsent)
                {
                    <div class="form-group">
                        <div class="form-check">
                            <input class="form-check-input" asp-for="RememberConsent">
                            <label class="form-check-label" asp-for="RememberConsent">
                                <strong>Remember My Decision</strong>
                            </label>
                        </div>
                    </div>
                }
            </div>
        </div>

        <div class="row">
            <div class="col-sm-4">
                <button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
                <button name="button" value="no" class="btn btn-secondary">No, Do Not Allow</button>
            </div>
            <div class="col-sm-4 col-lg-auto">
                @if (Model.ClientUrl != null)
                {
                    <a class="btn btn-outline-info" href="@Model.ClientUrl">
                        <span class="glyphicon glyphicon-info-sign"></span>
                        <strong>@Model.ClientName</strong>
                    </a>
                }
            </div>
        </div>
    </form>
</div>

Conroller Post 逻辑

        private async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model)
        {
            var result = new ProcessConsentResult();

            //  使用 IIdentityServerInteractionService  从returnUrl中得到请求上下文
            var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
            if (request == null) return result;

            ConsentResponse grantedConsent = null;

            //  当用户选择拒绝授权时 页面需要重定向到mvc客户端
            if (model?.Button == "no")
            {
                grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied };

                // 使用ids4 eventservice 发布拒绝事件
                await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues));
            }
            // 当用户选择允许授权
            else if (model?.Button == "yes")
            {
                // 授权客户端读取用户勾选的scope,即resourceowner 授予 第三方服务 访问 resource 的权限
                if (model.ScopesConsented != null && model.ScopesConsented.Any())
                {
                    var scopes = model.ScopesConsented;
                    if (ConsentOptions.EnableOfflineAccess == false)
                    {
                        scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
                    }

                    grantedConsent = new ConsentResponse
                    {
                        RememberConsent = model.RememberConsent,
                        ScopesValuesConsented = scopes.ToArray(),
                        Description = model.Description
                    };

                    // emit event
                    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)
            {
                // communicate outcome of consent back to identityserver
                await _interaction.GrantConsentAsync(request, grantedConsent);

                // indicate that's it ok to redirect back to authorization endpoint
                result.RedirectUri = model.ReturnUrl;
                result.Client = request.Client;
            }
            else
            {
                // we need to redisplay the consent UI
                result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
            }

            return result;
        }

// consent 确认表单提交
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Index(ConsentInputModel model)
        {
            var result = await ProcessConsent(model);

            if (result.IsRedirect)
            {
                var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);             
               
                return Redirect(result.RedirectUri);
            }

            if (result.HasValidationError)
            {
                ModelState.AddModelError(string.Empty, result.ValidationError);
            }

            if (result.ShowView)
            {
                return View("Index", result.ViewModel);
            }

            return View("Error");
        }

3 测试

  • 打开localhost:5010 重定向到登录页,然后从localhost:5030 登录成功

  • 授权成功 重定向到 localhost:5030 首页

问题

  • consent 同意之后的内部处理流程?

  • consent 拒绝之后客户端处理?

posted @ 2020-11-19 16:27  aimigi  阅读(488)  评论(0编辑  收藏  举报