前言
本篇说说ids中的网页登陆以及单点登陆的大致原理,主要是以基本跑通为目的,下一篇开始会详细说明集成ids网页登陆原理。
最好先熟悉以下知识:
- asp.net core
- asp.net core的身份验证和基于策略的授权
- identityServer官方文档过一遍
推荐蒋老师的《asp.net core 3 框架解密》
场景
你在访问一个网站登陆时,可以选择输入账号密码登陆,也可以选择第三方登陆,如:QQ、微博账号等。登陆流程我就不废话了。假设是QQ登陆,我们这里可以通过ids4来实现QQ服务器来向第三方应用提供身份验证的功能。
- 我们有多个MVC应用,假如是mvc1、mvc2,
- 希望统一由IndentityServer来做用户管理,假如这个服务叫idsServer
- 用户在登陆mvc1时自动跳转到idsServer的登陆页面,登陆成功后mvc1能拿到一个代表此用户的id(一个加密的字符串)
- 在我们后续请求mvc1时随时可以拿到用户的id
- 当请求mvc2时,由于是另一给应用,没登陆的情况下会跳转到idsServer去做登陆
- idsServer检测到这个浏览器之前在mvc1中做过登陆,直接返回用户id给mvc2
到此实现了mvc应用集成ids登陆,并实现了单点登陆。步骤5、6有点玄乎,下面会说明。ids4针对用户来说只是做身份验证(登陆),也就是识别出当前用户是谁,最终体现就是我们的应用可以拿到当前用户的id,ids4不负责用户的应用程序功能的授权,比如通常理解的基于角色的菜单、按钮权限
环境搭建
按官方文档的如下步骤可以搭建环境:
- https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html
- https://identityserver4.readthedocs.io/en/latest/quickstarts/2_interactive_aspnetcore.html
我们这里使用更直接一点的方式,使用它提供的项目模板一步到位
1、安装ids4的项目模板
dotnet new -i IdentityServer4.Templates
2、根据模板创建ids4项目
dotnet new is4inmem
此模板创建会直接帮你创建要给立即可用的ids服务应用,里面的客户端、资源、用户都是以内存的形式定义的。直接F5就可用跑起来
3、在ids服务端中注册mvc1、mvc2的配置
就是在ids登记下这俩客户端,我是ids,你俩要让我来帮你们做登陆得先来我这里登个记对吧
在Config.Clients中添加如下配置:
new Client { //客户端id ClientId = "mvc1", //客户端密钥 ClientSecrets = { new Secret("secret".Sha256()) }, //授权模式为code AllowedGrantTypes = GrantTypes.Code, //ids发放code时要回调客户端的地址 RedirectUris = { "https://localhost:5002/signin-oidc" }, //完成在ids中注销后回调客户端的这个地址 PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, //ids允许此客户端访问这些scope AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } }
mvc2的配置一样。
4、新建mvc1、mvc2客户端
5、配置mvc1客户端,mvc2类似略了
5.1、调整俩项目启动监听的端口,防止3个项目的端口冲突,我这里ids用的5001,mvc1用的5002,mvc2用的5003
5.2、引用nuget包
install-package Microsoft.AspNetCore.Authentication.OpenIdConnect
5.3、在startup.cs中做配置
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddControllersWithViews(); 4 JwtSecurityTokenHandler.DefaultMapInboundClaims = false;//还没研究过它是干啥的 5 services.AddAuthentication(options => //注册asp.net core 身份验证核心服务,并配置 6 { 7 options.DefaultScheme = "Cookies";//默认的身份验证方案名 8 options.DefaultChallengeScheme = "oidc";//用来跳转到dis登录页的身份验证方案名 9 //注意这俩配置与下面注册的身份验证方案的名字对应 10 }) 11 .AddCookie("Cookies")//注册asp.net core 默认的基于cookie的身份验证方案 12 .AddOpenIdConnect("oidc", options =>//注册ids为我们提供的oidc身份验证方案 13 { 14 options.Authority = "https://localhost:5001";//配置ids的根路径 15 options.ClientId = "mvc1";//此客户但的id 16 options.ClientSecret = "secret";//此客户端的密钥 17 options.ResponseType = "code";//授权模式 18 options.SaveTokens = true;//是否将最后获取的idToken和accessToken存储到默认身份验证方案中 19 }); 20 }
5.4、在startup.cs中做配置启用asp.net core的身份验证中间件
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 2 { 3 //略... 4 app.UseAuthentication(); 5 app.UseAuthorization(); 6 //略... 7 }
5.5、找个Controller的Action来充当受保护的页面,比如HomeController.Index
[Authorize] public IActionResult Index() { return View(); }
5.6、为了容易看到效果,可用修改下首页的视图,显示下当前登陆用户的信息
1 @using Microsoft.AspNetCore.Authentication 2 <h2>Claims</h2> 3 <dl> 4 @foreach (var claim in User.Claims) 5 { 6 <dt>@claim.Type</dt> 7 <dd>@claim.Value</dd> 8 } 9 </dl> 10 <h2>Properties</h2> 11 <dl> 12 @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items) 13 { 14 <dt>@prop.Key</dt> 15 <dd>@prop.Value</dd> 16 } 17 </dl>
跑起来
在解决方案上右键->属性
Ctrl + F5 走起...
此时由于mvc1和2的HomeController.Index是受保护的资源,首次访问因为用户并没有登陆,因此会调转到ids的登录页去
录入测试账号和密码,ailice点击登陆,此时会跳转到mvc1的首页,因为已登陆成功,所以此时页面可用正常方案
此时若去访问mvc2的首页https://localhost:5003/你会发现它会跳转以下,最后直接就可用访问,并不需要我们再登陆了,这就是所谓的单点登陆。
如何注销?
注销时要清楚本地登陆和ids那边的登陆状态,在mvc1和2的HomeController中加入如下Action
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
此时访问下这个Action就可用注销了。
主体流程
- 首先用户请求mvc1的受保护页面,mvc1的授权策略检测用户未登录,发出一个质询,由ids客户端库提供的身份验证处理器(在startup中配置的那个"oidc"的身份验证方案)处理这个质询,组织请求参数并跳重定向用户转到idsServer的登录页
- idsServer的AccountController.Login接收请求,通过相互服务接口IIdentityServerInteractionService对当前请求做验证(客户端啊、请求的scope啊、等等..),验证成功的话,得到一个结果AuthorizationRequest,表示当前授权请求,里面包含客户端id及其它参数
- 根据结果组织一个ViewModel,主要是决定是否显示第三方登陆(客户端请求时指定了希望哪种登陆方式?客户端配置时指定了支持哪些验证方式?idsServer默认支持哪些验证方式?)
- 假如用户使用账号密码登陆,输入后提交
- AccountController.Login Post接收请求,做步骤2一样的事,验证下客户端以及其它参数的验证,若通过则验证用户账号密码,若成功则得到用户实体(ids中注册的用户信息)
- ids自己做本地登陆,将用户信息加密存储到cookie中,然后跳转到自己的/connect/authenraztion/callback终结点
- 在/callback终结点中先验证客户端提交的各参数,生成临时code,回调客户端的".../signin-oidc"
- 客户端携带clientid 密钥 code 之类的参数找ids请求idToken和accessToken
- ids返回idToken和accessToken,mvc1服务端存储它们,然后将用户标识加密存储到用户的cookie中
- 用户后续携带用户标识cookie请求mvc1就可用了
- mvc1有时候需要携带accessToken访问被ids保护的第三方接口
- 当用户发起注销调用Home/logout时,在mvc1中注册的两个身份验证方案都会执行,"Cookies"将情况用户本地保存用户标识的cookie,“oidc”会与ids通信,删除ids存到用户浏览器中的cookie,此时ids还会以某种机智通知到其它已登陆的客户端,如何通知的后面再说
单点登陆的重点
按流程看,用户浏览器存储了两份代表用户标识的cookie,一个在idsServer域名下,要给在mvc1域名下,当mvc跳转到ids登录页时,会携带ids域名下的cookie
ids一看,这用户登陆过,就直接携带用户信息和token并跳转到回调客户端的".../signin-oidc",客户端的后续步骤不变
结尾
本篇只是草草说了各大概,下一篇会先说说ids网页登陆里涉及到的交给核心类,之后会重新按这里的流程走走源码...