zoukankan      html  css  js  c++  java
  • IdentityServer网页登陆-登陆原理

    前言

    现代程序开发中身份验证、授权是一件非常非常复杂的事情(各种登陆方式、各种授权需求、各种跳转跳、各种加解密,搞得得头皮发麻),因为事情本身复杂,所以没把这件事理清楚之前,无论你用什么语言、什么框架、什么方式都很难做到既简单又具有可扩展性。我的想法是既然我自己做不到,那就搞懂身份验证和授权是咋回事,再学习一套优秀的开源框架。这两件事其实就是一回事。

    之前从asp.net core的源码开始学起,后来陆续看了asp.net core的身份验证和授权相关源码,之前也写过几篇相关博客。最近几天一直在看identityserver源码,目前对整套东西有个大概了解了,这个过程很痛苦也很有收获。按个人经验来说推荐如下学习方式:

    1. 先过一遍蒋老师的《asp.net core 3框架解密》,里面有asp.net core的原理,包括身份验证和授权的详解
    2. 过一遍IdentityServer官方文档
    3. 看IdentityServer源码

    学完你会掌握asp.net core和identiyServer的机制,同时学学大牛的软件设计思路。

    IdentityServer本身支持多种身份验证和授权的流程,这里只是针对普通mvc程序作为客户端,集成ids网页登陆的流程进行说明。搞懂这一个流程,其它流程也差不太远。这里只是将主体流程,里面涉及到具体步骤已经连接到在上一篇《IdentityServer4种的核心类》中。建议先看看官方文档的《Protecting an API using Client Credentials》、《Interactive Applications with ASP.NET Core

    写文章时IdentityServer4是4.x版本,下面IdentityServer简称ids/ids4,这里主要是根据源码讲原理,如果你只是想简单使用ids,看官方文档更合适,可以快速上手。

    一、跳转到登录页idsServer/Account/Login(Get请求)

    总结:根据请求参数决定显示哪些第三方登陆

    当用户访问客户端(这里的mvc应用,对于ids来说就是客户端)受保护的页面时,若没有登陆则会跳转到ids的登陆页面。至于如何实现跳转的这是客户端配置的ids身份验证方案来完成的,不是本篇重点。

    具体来说是get请求跳转到AccountController.Login(returnUrl),returnUrl参数是客户端配置的ids身份验证方案生成的,里面包含ids授权需要的重要参数。(注意授权请求的参数并不是直接放在queryString里的,而是放在queryString里的returnUrl这个参数中的)returnUrl的格式如下:

    /connect/authorize/callback?
    client_id=mvc2&                      --客户端id好理解
    redirect_uri=https://localhost:5003/signin-oidc&  --当ids生成code之后回调客户端的这个地址,客户端在这个里会携带客户端密钥和其它参数再次请求ids,以获取accessToken/idToken  
    response_type=code&                    --希望ids响应的类型
    scope=openid profile&                   --客户端希望请求的资源范围
    code_challenge=MstxPB26Nhnm2HvxVwz7TeFjyvQ6EDzvXp0xH-L7OYk& --目前不晓得啥用,应该是个签名密钥
    code_challenge_method=S256&                    --目前不晓得啥用,应该是签名算法
    response_mode=form_post&                      --当ids生成code之后以何种方式回调客户端的“/signin-oidc”,显然这里会动态生成一个form,然后post提交
    nonce=637338663012806688.YWI3MjIxZGItMzlmOC00NmRiLTkz     --随机字符串,参与签名计算
    MTMtNGFjNDJiMDczNWQzM2Q5NjE4ZjMtZmIwNS00ODAxLWI0ZmQtZ
    TAxZDY2NDQyNDlk&                  
    state=CfDJ8FuK5UM4huhIoCCo0DRW8LkJjSHkSLmZjj5kYC5BhPOFqfUWAq2Fq6QzWDrqM9W6j2oNMIRmg6Len1JTzHa5uhBh-5Ij0jzrg7vUd5Z-jE1KwRno    --客户端的一个状态字段,将来回调客户端时会原样携带回去
    siu3W7hRLc2TKUeviyVd8zoBy1kVj5pmyhNWdahfQLU9lZgPQZBRe7U_itnUUo2S_hO4D1Rm7vq5uJwWTyn_00HNpZCMzVfZyWP2hCPGCRBCJtowq6yhbQ4qD_
    L_jPo67MKEjLPEDolw857KkCKPYzu_HlSK1WyiKBTr1-H0yp19p0atpHNOKqtXyLbdY5Lsej0OcQLO99bE1v0LcvRJwyH758ZsbfLRaTUr7O6rI-wXBe6dp3ny
    wkFcrW16la09ZPi8rvoUkDQZhhs8qA&

    x-client-SKU=ID_NETSTANDARD2_0&
    x-client-ver=5.5.0.0

    下面是get请求Loigin方法的源码

    1 public async Task<IActionResult> Login(string returnUrl)
    2 {
    3     var vm = await BuildLoginViewModelAsync(returnUrl);
    4     if (vm.IsExternalLoginOnly)
    5         return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
    6     return View(vm);
    7 }

    BuildLoginViewModelAsync用来创建一个包含第三发登陆信息的viewModel,用来显示到登陆页面上,如何确定要显示哪些第三方登陆的呢?,由于方法内容稍多,我下面直接说此方法的核心步骤:

    1. 使用交互服务接口IIdentityServerInteractionService验证returnUrl中的参数,并根据这些参数创建一个表示当前请求的上下文对象AuthorizationRequest 此步骤很常用
    2. 若前端有通过acr_values参数来指定希望通过哪种身份验证方案,若指定的方案是ids本地用户登陆,则不显示三方登陆,否则第三方登陆里只包含客户端指定的这个第三方登陆
    3. 若客户端没有指定,则获取ids允许的所有身份验证方案(启动配置时定义的支持的多种身份验证方案)
    4. 通过client_id参数从配置中找到客户端实体,看是否启用ids本地用户登陆。
    5. 客户端若配置了IdentityProviderRestrictions属性,则与步骤3中做交集来最终确认在页面要显示哪些第三登陆
    6. 若请求参数指定了LoginHint参数,则将其作为登陆的默认用户名

    这些步骤都是来设置viewModel的相关属性,这个viewModel决定视图页面要显示哪些内容

    二、Post提交账号密码到idsServer/Account/Login

    总结:验证用户账号密码 --> 加密得到的用户 --> 写入idsServer域名下的cookie --> 将用户重定向到"idsServer/connect/authenzation/callback"

    用户输入完账号密码,连同前一步骤的returnUrl(里面包含授权请求的重要参数)Post提交到ids的AccountController.Login这个Action中,下面看看其核心步骤:

    1. 使用交互服务接口IIdentityServerInteractionService验证returnUrl中的参数,并根据这些参数创建一个表示当前请求的上下文对象AuthorizationRequest 此步骤很常用
    2. 否则若用户点击是取消,
      1. 则调用交互服务接口IIdentityServerInteractionServiceDenyAuthorizationAsync在加密cookie中记录用户的拒绝授权的操作
      2. 将用户重定向到"idsServer/connect/authenzation/callback",授权的参数也继续传递过去
    3. 验证用户名和密码,若成功则从配置中获取用户实体,同时处罚UserLoginSuccessEvent事件
    4. 使用ids的本地身份验证方案(cookie)做登陆,写入加密的用户信息和相关的身份验证属性AuthenticationProperties
    5. 将用户重定向到"idsServer/connect/authenzation/callback",授权的参数也继续传递过去

    三、ids使用AuthorizeCallbackEndpoint处理"callback回调

    处理用户对授权scope的确认、ids中client、resouce和scope的配置最终得出可以授权哪些scope,最后生成code并跳转到mvc客户端回调地址

    核心步骤:

    1. 通过本地身份验证方案(cookie)中解密得到用户信息
    2. 通过用户授权确认信息存取器IConsentMessageStore获取用户授权确认信息ConsentResponse,比如用户最终允许了哪些scope。由于之前这个信息存储在加密cookie中的,这时会删除掉
    3. 通过授权请求验证器IAuthorizeRequestValidator验证授权请求参数,然后转换为表示当前授权请求的AuthorizeRequestValidationResult
    4. 使用授权交互响应构建器IAuthorizeInteractionResponseGenerator检查用户是否需要登陆、授权确认信息、将最终授权的resource scope等信息设置到表示当前授权请求的对象中
    5. 通过授权响应构建器IAuthorizeResponseGenerator生成响应,由于这里是ids端的回调,因此此步骤主要是生成存储code的响应对象AuthorizeResponse
    6. 最后响应时动态创建一个form表单提交到mvc客户端的“mvc1/oidc-callback”地址

    四、客户端携带client_id、client_securet、code等信息向ids请求accessToken/idToken

    此步骤是ids提供的客户端库来完成的,暂时忽略。我们只要知道此时会携带重要的参数:client_id、client_securet、code 去向ids的Authenrize/token端点请求accessToken/idToken就行了

    五、ids验证客户端密钥并发放token

    客户端携带client_id|、密码、code和其它参数请求ids的Token端点,ids验证客户端密钥、code等,最后发放accessToken/idToken,核心任务如下:

    1. 请求由ids的TokenEndpoint接管,执行ProcessAsync
    2. 客户端密钥验证器IClientSecretValidator验证客户端的密钥
    3. token请求验证器ITokenRequestValidator验证当前请求,主要是对授权类型等参数进行验证,然后验证code
    4. 使用token响应构建器ITokenResponseGenerator创建响应TokenResponse
    5. 将TokenResponse包装为TokenResult,它类似mvc里的actionResult的设计,这个result对象将在ids的中间件中被执行,执行时响应token给mvc客户端

    六、mvc客户端做本地登陆并跳转到redirectoryUrl

    此步骤是ids提供的客户端库来完成的,暂时忽略。我们只要知道

    客户端可以直接拿到用户标识openid,做本地登陆。如使用asp.net core常用的基于cookie的身份验证登陆,本质是加密用户信息和验证相关的属性存储到cookie里

    也可以根据标识openid去mvc本地系统的用户管理模块中找到匹配的用户,若没有则自动或提示用户注册或绑定现有账号。最后以mvc本地的系统用户做登陆,此时登陆同理使用asp.net core基于cookie的登陆

    七、结束

    这里聊了下ids作为统一的身份验证和授权服务里中的网页登陆,类似于平时集成qq登陆的场景,里面主要描述了这个流程中的主要过程,涉及到具体步骤或类基本都有连接,可以点进去详细看。没有介绍基础知识,建议看源码学习ids时可以作为参考。这种场景我们平时可能不太会用,下一篇也许会写个常见的”资源所有者密码模式“的过程。

  • 相关阅读:
    Java实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 697 数组的度(类似于数组的map)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 696 计数二进制子串(暴力)
    Java实现 LeetCode 695 岛屿的最大面积(DFS)
    Java实现 LeetCode 695 岛屿的最大面积(DFS)
    PHP serialize() 函数
    PHP print_r() 函数
  • 原文地址:https://www.cnblogs.com/jionsoft/p/13556100.html
Copyright © 2011-2022 走看看