IdentityServer4 综合应用实战系列 (一)登录
这篇文章主要说登录,这里抛开IdentityServer4的各种模式,这里只说登录
我们要分别实现 4中登录方式来说明, IdentityServer4本地登陆 、 Windows账户登录(本地的电脑用户)、微信登录、其他IdentityServer4认证的用户,为此我做了一个登录页面 如下图:

1 IdentityServer4本地登陆
要实现本地登录,我们需要去构建页面,分析页面上的字段元素,可以参考IdentityServer4官方的QuickStart,我这里自己写了一下,跟官方还是有出入的,基础的就不特别说明了直接贴上代码:
/// <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登录,转到了如下图所示 已经登录成功了


最后:真对不同的登录方式,提供不同的扩展登录鉴权即可
IdentityServer4 综合应用实战系列 (一)登录的更多相关文章
- Spark入门实战系列--2.Spark编译与部署(上)--基础环境搭建
[注] 1.该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取: 2.Spark编译与部署将以CentOS 64位操作系统为基础,主要是考虑到实际应用 ...
- Spark入门实战系列--2.Spark编译与部署(下)--Spark编译安装
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .编译Spark .时间不一样,SBT是白天编译,Maven是深夜进行的,获取依赖包速度不同 ...
- Spark入门实战系列--5.Hive(上)--Hive介绍及部署
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .Hive介绍 1.1 Hive介绍 月开源的一个数据仓库框架,提供了类似于SQL语法的HQ ...
- Spark入门实战系列--6.SparkSQL(下)--Spark实战应用
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .运行环境说明 1.1 硬软件环境 线程,主频2.2G,10G内存 l 虚拟软件:VMwa ...
- Spark入门实战系列--9.Spark图计算GraphX介绍及实例
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .GraphX介绍 1.1 GraphX应用背景 Spark GraphX是一个分布式图处理 ...
- AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型 . AspNetCore-MVC实战系列(二)之 ...
- AspNetCore-MVC实战系列(三)之个人中心
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . git源码:https://github.com/shenniubuxing3/LovePicture.Web . AspNetC ...
- MP实战系列(七)之集成springboot
springboot是现在比较流行的微服使用的框架,springboot本质上就是将spring+springmvc+mybatis零配置化,基本上springboot的默认配置符合我们的开发.当然有 ...
- MP实战系列(八)之SpringBoot+Swagger2
SpringBoot一个原则,爱好编程的朋友们都知道,那就是"习惯优于配置". 今天一上来主要说的还是代码,个人比较喜欢来的实战系列的,不过有的时候还是比较偏重于理论,理论是造轮子 ...
随机推荐
- 【编程思想】【设计模式】【行为模式Behavioral】中介者模式Mediator
Python版 https://github.com/faif/python-patterns/blob/master/behavioral/mediator.py #!/usr/bin/env py ...
- 阿里巴巴Java开发手册摘要(二)
MySql数据库 一建表规约 1.表达是与否概念的字段,必须使用is_xxx的命名方式,数据类型是unsigned tinyint(1:是,0否) 正例:表达逻辑删除的字段名is_deleted,1表 ...
- Docker(4)-docker常用命令
帮助命令 docker version # 查看docker的版本信息 docker info # 查看docker的系统信息,包含镜像和容器的数量 docker --help # 帮助命令 dock ...
- Redis持久化 aof和rdb的原理配置
目录 一.介绍 二.RDB持久化(全量写入) rdb原理 rdb模式 rdb触发情况 rdb优势和劣势 rdb文件配置 rdb命令配置 rdb数据恢复 三.AOF持久化(增量写入) aof原理 aof ...
- [BUUCTF]PWN——ciscn_2019_n_3
ciscn_2019_n_3 附件 步骤 例行检查,32位,开启了nx和canary保护 本地试运行一下,经典的堆题的菜单 3.32位ida载入 new(),申请了两个chunk,第一个chunk(1 ...
- CF312A Whose sentence is it? 题解
Content \(\texttt{Freda}\) 和 \(\texttt{Rainbow}\) 在网上聊了 \(n\) 句话.我们根据他们聊天的语句的特点来判断每一句是谁说的.\(\texttt{ ...
- java 多线程:线程安全问题synchronized关键字解决
背景: 多个线程同时修改一个变量时,有概率导致两次修改其中某些次被覆盖. 例如:如下案例一个变量值为3,三个线程同时对其-1,如果按顺序执行,每次减完的结果应该是2,1,0.但实际运行中有可能变为0, ...
- CentOS7.6 鲜为人知的/etc/resolv.conf 之 /etc/resolv.conf.save (保持/etc/resolv.conf不被修改:/etc/dhcp/dhclient-enter-hooks 无效之/etc/resolv.conf被清空的特殊案例)
目的: 用户可以自定义/etc/resolv.conf内容,且不被系统修改. 常规方法1: /etc/sysconfig/network-scripts/ifcfg-eth0 网卡配置文件中增加PEE ...
- mysql数据库总结笔记
一.安装和配置数据库: 下载mysql地址:https://dev.mysql.com/downloads/mysql/ windows下载的版本是installer msi版本:https://de ...
- Spring学习(一)idea中创建第一个Spring项目
1.前言 Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container). Sprin ...