IdentityServer4 comprehensive application of actual combat series (a) Log

This article says login here put aside the various modes IdentityServer4, saying only that login here

We want to achieve, respectively, in 4 login to explain, IdentityServer4 local login, Windows login account (local computer users), micro-channel login, IdentityServer4 other authentication of users, and I had a login page as shown below:

 

1 IdentityServer4 local landing

To achieve log on locally, we need to build pages, analyze field elements on the page, refer to IdentityServer4 official QuickStart, I wrote about myself here, with the official or discrepancies, based on the special instructions not directly pasting the code :

/// <summary>
    /// 登录页面需要的模型
    /// 事想登录界面 有用户名 、密码 、记住登录状态、以及外部提供的各种第三方扩展登录信息
    /// 
    /// </summary>
    public class Idr4LoginInfoModel
    {


        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }

        public string Password { get; set; }

        /// <summary>
        /// 登录回调地址
        /// </summary>
        public string BackUrl { get; set; }
        /// <summary>
        /// 是否是Idr本地当前页面登录,这里也可以根据数据的客户端的配置来处理
        /// </summary>
        public bool IsLocalLogin { get; set; } = true;


        /// <summary>
        /// 是否允许记住登录状态 默认是true
        /// </summary>
        public bool IsRememberLogin { get; set; } = true;

        public bool IsRememberLoginDefaultStatus { get; set; } = false;
        /// <summary>
        /// 扩展登录  ExternalProvider 主要是为了记录 Scheme信息,这里我修改一个名称
        /// </summary>
        /// Enumerable.Empty<ExternalProviderModel>();
        public IEnumerable<ExternalProviderModel> ExtendLoginProviders { get; set; } 



        /// <summary>
        ///  登录页面是否只支持扩展登录使用
        /// </summary>
        public bool IsOnlyExtendLogin  => IsLocalLogin == false && ExtendLoginProviders?.Count() == 1;
        /// <summary>
        /// 获取扩展授权策略的信息
        /// </summary>
        public string ExtendLoginScheme => IsOnlyExtendLogin ? ExtendLoginProviders?.SingleOrDefault()?.SchemeName : null;
        /// <summary>
        /// 错误信息提示
        /// </summary>
        public ErrorModel errorModel { get; set; }


    }
Idr4LoginInfoModel
    public class ExternalProviderModel
    {
        /// <summary>
        /// 扩展授权的策略  类似 QQ  需要一个策略 以及一个显示的名称
        /// </summary>
        public string SchemeName { get; set; }

        /// <summary>
        /// 显示第三方扩展登录的名称 比如 QQ  微信 等
        /// </summary>
        public string DispalyName { get; set; }


    }
}
ExternalProviderModel

这些代码就是为了构建页面需要的元素而写的,可以凭借自己的喜好来玩耍,然后就是在页面上按逻辑构造好基础的页面。

接下来是该处理登录的业务逻辑了,这里就需要对IdentityServer4进行配置了,配置基础操作就直接掠过了,官方也是有的,注意一点就是指定好相关的登录退出页面以及参数,这里贴一下其中的页面路径配置,我这里修改了参数的名称,这个无所谓

options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions
                {
                    LoginUrl = "/account/login",
                    LogoutUrl = "/account/logout",
                    LoginReturnUrlParameter = "backurl",
                    LogoutIdParameter = "logoutid"

                };

然后继续写后面的逻辑代码,准备控制器 方法 Login,贴一段构造的模型代码,具体都可以视情况而定 , 我这里代码里面模拟写了用户的验证,为了演示测试,具体结合自己业务处理即可,也添加了一些说明注解

 private async Task<Idr4LoginInfoModel> BuildIdr4LoginInfoModelAsync(string backurl)
        {
            //1、获取授权信息上下文对象

            var authcontext = await _identityServerInteraction.GetAuthorizationContextAsync(backurl);
            //2、判断授权是否是外部扩展提供的,当然外部提供的 需要外部相关的交互接口来找到对应提供授权方案的类

            //3、存在外部授权方案  利用授权策略提供服务获取相关信息 authcontext?.IdP 相关的 scheme 的策略方案类型  如:cookies
            if (authcontext?.IdP != null && await _schemeProvider.GetSchemeAsync(authcontext.IdP) != null)
            {
                //判断是不是idr4的提供外部登录,根据backurl只能触发一个外部扩展登录
                var isIdr4Provider = authcontext.IdP == IdentityServerConstants.LocalIdentityProvider;
                var loginVm = new Idr4LoginInfoModel
                {
                    IsLocalLogin = isIdr4Provider,
                    UserName = authcontext?.LoginHint
                };
                //不是Idr4提供的
                if (!isIdr4Provider)
                {
                    //显示扩展登录 构造相关模型
                    loginVm.ExtendLoginProviders = new[] { new ExternalProviderModel { DispalyName = authcontext.IdP } };
                }
                return loginVm;

            }
            //4、不是Idp扩展登录,获取所有的授权策略信息

            //都是为了获取中间件授权中的信息 然后展示到登录界面上 供用户选择提供的第三方扩展登录
            var schemeall = await _schemeProvider.GetAllSchemesAsync();
            //查找到 Windows 登录的策略 或者 DisplayName不是空的策略信息
            var allextendprovider = schemeall.Where(c => c.DisplayName != null || c.Name.Equals("Windows", StringComparison.OrdinalIgnoreCase))
                .Select(x =>
                {
                    return new ExternalProviderModel
                    {
                        DispalyName = x.DisplayName,
                        SchemeName = x.Name
                    };
                }).ToList();

            //5、判断客户端信息
            if (authcontext?.ClientId == null)
            {
                var loginVm = new Idr4LoginInfoModel
                {
                    IsLocalLogin = true,
                    IsRememberLogin = true, //常量的配置后面统一来处理
                    BackUrl = backurl,
                    UserName = authcontext?.LoginHint,
                    ExtendLoginProviders = allextendprovider.ToArray()
                };
                return loginVm;
            }
            //6、 通过数据库交互查询可用的客户端信息 这里需要的交互接口对象是 IClientStore
            var clientinfo = await _clientStore.FindEnabledClientByIdAsync(authcontext.ClientId);
            if (clientinfo == null)
            {
                var loginVm = new Idr4LoginInfoModel
                {
                    errorModel = new ErrorModel
                    {
                        IsError = true,
                        ErrorMsg = $"不存在ClientID:{authcontext.ClientId}的客户端信息"
                    }
                };
                return loginVm;

            }
            else
            {
                var loginVm = new Idr4LoginInfoModel
                {
                    IsLocalLogin = clientinfo.EnableLocalLogin,
                    IsRememberLogin = true, //常量的配置后面统一来处理
                    BackUrl = backurl,
                    UserName = authcontext?.LoginHint,
                    ExtendLoginProviders = allextendprovider.ToArray()
                };
                return loginVm;

            }






        }
BuildIdr4LoginInfoModelAsync
[HttpGet]
        [Route("login")]
        public async Task<IActionResult> Login(string backurl)
        {
            //Idr根据客户端配置的情况 拼接好相关的参数 转到登录界面

            //1、根据backurl校验信息,并获取构建登录页面需要的信息,需要Idr4提供的交互接口 IIdentityServerInteractionService


            var logininfo = await BuildIdr4LoginInfoModelAsync(backurl);
            //仅仅扩展登录
            if (logininfo.IsOnlyExtendLogin)
            {
                //交互授权信息
                return RedirectToAction("ExtendLogin", new { provider = logininfo.ExtendLoginScheme, backurl });
            }

            return View(logininfo);


        }
Login Get
      [HttpPost]
        [Route("login")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(Idr4LoginInfoModel loginInfoModel)
        {
            //校验授权相关信息
            var context = await _identityServerInteraction.GetAuthorizationContextAsync(loginInfoModel.BackUrl);


            //点击取消 提供给第三方的登录取消的情况

            //PKCE(RFC7636)是授权码流程的扩展,以防止某些攻击,并能够安全地执行来自公共客户端的OAuth交换。
            //
            if (HttpContext.Request.Form["button"] == "cancel")
            {
                if (context != null)
                {
                    //这里为什么要使用这个方法 本来这个流程就是为了提供第三方登录的,这里肯定要转到GrantConsent失败
                    await _identityServerInteraction.GrantConsentAsync(context, ConsentResponse.Denied);
                    if (Url.IsLocalUrl(loginInfoModel.BackUrl))

                        //这里需要验证PKCE是否设置,获取Client信息,可以自定义处理
                        if (!string.IsNullOrEmpty(context.ClientId) && await CheckClientPKCE(context.ClientId))
                        {
                            //这里可以定义一个页面 记得在Idr3中没有这个设置,记得当时为了处理一个过度页面,特地的改了源码中的oidc/signin document.write 的post提交页面
                            // return Redirect(loginInfoModel.BackUrl);

                            //这里有了这个页面后我们就可以来过度了,这个过度页面也可以用在退出上面,所以这里我改版了下,为了用统一页面
                            return View("Redirect", new RedirectModel { Topic = "登录中......", BackUrl = loginInfoModel.BackUrl });

                        }

                    return Redirect(loginInfoModel.BackUrl);


                }

                else
                {
                    return Redirect("~/");
                }




            }
            //确认登录

            //验证用户账户相关
            if (ModelState.IsValid)
            {
                //验证用户密码 (这里可以使用自己的数据来验证就行了)

                //备注:这里的登录只是扩展

                if (loginInfoModel.UserName.Equals("admin"))  //数据校验成功
                {
                    //通过事件 这里可以通过事件配置来设置通知事件

                    //定义身份证
                    var _claimidentity = new ClaimsIdentity("MYCardID");
                    _claimidentity.AddClaim(new Claim("name", "admin"));
                    _claimidentity.AddClaim(new Claim("sub", "admin_1_0"));
                    var _claimprincipal = new ClaimsPrincipal(_claimidentity);
                    await HttpContext.SignInAsync("Cookies", _claimprincipal);

                    //接下来是跳转的问题 ,这里存在两种情况
                    if (context != null)
                    {
                        if (!string.IsNullOrEmpty(context.ClientId) && await CheckClientPKCE(context.ClientId))
                        {
                            return View("Redirect", new RedirectModel { Topic = "登录中", BackUrl = context.RedirectUri });
                        }

                        return Redirect(context.RedirectUri);
                    }
                    else
                    {

                        return Redirect("~/");
                    }

                }


            }

            //

            return View();


        }
Login Post

待这些处理OK了后,来测试下,代码中需要添加授权标签 以及认证的中间件 基操就掠过

 

 

 2、Windows用户登录(本机用户)

前面的代码中我该关于Windows模型中构建了,但是对于这种扩展登录我们需要添加扩展登录方法,官方的QuickStart里面是有的,我这里也基本一样,代码就不贴出来了

这里需要注意 3点

1、Windows用户登录要基于IIS来设置,请用IISExpress运行

2、项目中的  iisSettings 中的 windowsAuthentication 设置 true 允许Windows认证 

3、按以上2点启动项目后,系统已经默认添加了 Windows的认证策略 Scheme,不需要在加了,但是 DisplayName是没有的,我们可以在页面上稍微处理下即可

接下来按 QuickStart上面的流程操作下,输入电脑的用户名密码登录后可以看到本机电脑相关的信息

 

 

 

 

 3、微信登录

微信登录相对要繁琐一些,为此我自己写了一个基于OAuth2的微信登录中间件,Nuget上面有很多这样的库 nuget搜索 ,项目请使用.NetCore 3.1 

 

 

 为此我们需要申请一个微信的测试号来测试,由于微信对浏览器的限制,可以采用微信开发工具中的  微信公众号网页 来测试

微信测试号申请地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 扫码登录看到

 

 

 测试改一下如图中的这个地方就够了

 

 这里需要注意的是,由于不支持localhost,请使用IP地址,记得不需要http,不然会有回调地址错误的问题,测试账户需要关注这个测试公众号才可以哦

接下来就是使用我们的中间件来实现微信登录了,注册好服务,如下代码

  services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
             
            .AddCookie()
            .AddWeChat(options =>
            {
                options.AppId = "id";
                options.AppSecret = "password";

            });
AddServices

中间里面我获取了微信用户的相关信息,接下来用微信测试工具来看下,访问下需要授权的页面,转到了登录页面 如图:

 

 

点击微信的登录进入并同意相关信息授权访问,可以看到已经登录成功了

 

 

 4、其他IdentityServer4认证的用户

 鉴于这种操作,还是基于OAuth2来实现,为此我准备了一个用IdentityServer4做的授权认证服务端 http://192.168.0.212:40000 ,开始撸代码了,基于OAuth2来实现下,下面继续添加代码,在上面的代码后面添加如下代码

  services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
             
            .AddCookie()
            .AddWeChat(options =>
            {
                options.AppId = "id";
                options.AppSecret = "secret";

            })
               .AddOAuth("OAuth", "第三方(IdentityServer4)", configureOptions =>
                {
                    configureOptions.ClientId = "testcode";
                    configureOptions.ClientSecret = "testcode";
                    configureOptions.TokenEndpoint = "http://192.168.0.212:40000/connect/token";
                    configureOptions.AuthorizationEndpoint = "http://192.168.0.212:40000/connect/authorize";
                    configureOptions.UserInformationEndpoint = "http://192.168.0.212:40000/connect/userinfo";
                    configureOptions.CallbackPath = "/codecallback";
                    configureOptions.Scope.Add("openid");
                    configureOptions.Scope.Add("profile");
                    configureOptions.SaveTokens = true;
                    configureOptions.Events = new OAuthEvents
                    {
                        OnTicketReceived = TicketReceived,
                        OnCreatingTicket = CreatingTicket,
                        OnRemoteFailure = RemoteFailure,
                        OnAccessDenied = AccessDenied,

                    };

                })
               ;
AddServices

这里处理了关键事件的重写用来处理远程登录过程中的一些错误、失败、票据信息的处理,基础操作直接掠过,比如通过 获取AccessToken 和 获取用户信息并写如到 身份声明中

下来试一试,点击第三方的IdentityServer4登录,转到了如下图所示 已经登录成功了

 

 

 

 

 最后:真对不同的登录方式,提供不同的扩展登录鉴权即可

 

Guess you like

Origin www.cnblogs.com/liyouming/p/12108541.html