上一篇文章我们介绍了Asp.net core中身份验证的相关内容,并通过下图描述了身份验证及授权的流程:
  
  注:改流程图进行过修改,第三方用户名密码登陆后并不是直接获得code/id_token/access_token,而是登录后可以访问identityServer中受保护的资源(Authorize Endpoint),通过发起身份验证请求来实现授权码流程、隐式流程及混合流程来完成token的获取,它与直接通过用户名密码来获取token的Oauth2.0 Password GrantType方式是不一样的。
  在asp.net core应用程序中,通过授权码流程可以使用第三方(IdentityServer)的用户名密码,经过一系列的token、userinfo获取,最后生成身份信息载体(Cookie),asp.net core应用程序使用cookie就能完成身份验证工作。这个过程对于用户来说,它与一般的asp.net core应用程序(特指基于asp.net core identity的应用程序)是没有任何区别的,都是通过用户名密码登录,然后就可以进入系统。对于应用程序来说它仍然是基于cookie来完成身份验证,只不过生成cookie所需的数据是第三方提供的而已。
  但是单页应用由于其特殊性,其UI渲染工作及业务逻辑的处理都是由浏览器完成,服务器不具备相关功能(仅需静态文件传输即可),其次单页应用会存在跨域问题,所以cookie就不适合作为单页应用的身份信息载体,本文就介绍如何使用jwt来完成单页应用的身份验证。
  主要内容有:
  • 创建一个简单的单页应用项目
  • 使用单页应用完成受保护资源访问
  • oidc Client简介
  • oidc-client.js的各种用法
    • 弹出式登录/登出
    • 静默登录与静默刷新
    • 会话监听
  • 小结

创建一个简单的单页应用项目

  注:该单页应用完全参考官方文档,本小节仅对要点进行介绍,详情参见文档:https://identityserver4.readthedocs.io/en/latest/quickstarts/4_javascript_client.html
  1、新建一个asp.net core的web应用程序:
  2、添加oidc的js组件:
  可以通过npm进行安装或者在Github上直接下载,以下为npm安装方法:
  npm i oidc-client
  
  完成之后在相关目录下可找到oidc-client相关文件:
   
  将其复制到适合的位置即可。
  3、通过数据库添加一个Client信息(如果是基于内存的,那么需要添加一个client实例,详见文档:https://identityserver4.readthedocs.io/en/latest/quickstarts/4_javascript_client.html#add-a-client-registration-to-identityserver-for-the-javascript-client),用于单页应用的授权配置:
  添加Client时需要注意几个关键信息:
  • 授权类型支持授权码类型;
  • 不需要客户端密码;
  • 允许跨域,允许客户端跨域访问IdentityServer;
  参考如下,内存实例:
  
  数据库数据:
  
  4、添加基于oidc-client的登录、API访问以及登出业务逻辑代码App.js:
  UserManager对象初始化:
  
  使用UserManager实例进行登录跳转:
  
  使用UserManager实例获取用户信息,然后通过用户信息中的access token访问受保护资源:
  
  使用UserManager实例进行登出跳转:
   
  5、添加功能页面Index.html,包含登录、API访问及登出功能:
  
  6、添加用于处理授权码的重定向页面:
  
  到此单页应用程序已经创建完毕,后面就使用该程序要介绍它是如何完成身份验证,并访问受保护资源的。

使用单页应用完成受保护资源访问

  我们使用一个简单的asp.net core web api项目(本系列文章用过的)来进行演示,它对于普通API项目来说要点在于:
  1、添加基于JwtBearer的身份验证处理器:
  
  2、添加跨域处理,添加跨域策略配置:
  
  3、在asp.net core应用请求管道中应用跨域配置:
  
  4、受保护内容通过authorize特性进行标记:
  
  一切准备就绪后运行三个应用程序,单页应用运行并打开index.html页面效果如下,一共有三个功能,登录、调用API以及登出:
  
  登录:它实际上是调用oidc Client的signinRedirect方法,语义上来说它是通过重定向的方式进行登录,而它实际执行的效果如下:
  跳转到了IdentityServer的登录页面,然后我们再看看它本质上是做了什么?
  它实际上是发起了一个授权码流程的身份验证请求(请求过程可参考:https://www.cnblogs.com/selimsong/p/14355150.html#oidc_code_flow),发起请求后,由于当前用户没有在IdentityServer上登录或者说未通过IdentityServer的身份验证,所以由跳转到登录页面。
  当我们通过用户名密码登录之后,IdentityServer将继续完成授权码流程,后续流程是生成相应的授权码并返回到客户端配置的重定向uri(本例中是https://localhost:5003/html/callback.html),
为了能够看清楚整个请求过程,本例在callback.html页面加入了调试断点:
  
  断点位于signinRedirectCallback方法之后,也就是完成回调处理之后(这个时候已经完成token等信息的获取),跳转到index.html页面之前。
  以下是输入用户名密码提交后命中断点时的相关请求信息:
  由上图可以看到,当输入用户名密码提交后(第一个请求),由于通过了身份验证,那么继续完成授权码流程(第二个请求),授权码流程完成后携带授权码重定向到Client配置的重定向地址(第三个请求).
  第三个请求就到了我们的callback.html页面,页面的加载首先请求了oidc-client.js文件,然后由UserManager的实例化以及signinRedirectCallback方法,来完成了后续请求,后续请求包含openid的配置信息请求、获取Token请求、获取用户信息(userinfo)请求以及检查会话请求。
  以上一系列的请求结果就是在浏览器的会话存储中,我们可以找到相关的数据信息:
  
  断点通过后就来到了index.html页面,并打印出登录用户信息:
  
  点击Call API按钮后,程序将从存储信息中获取到access_token,携带access_token完成请求:
  点击登出按钮后,程序将删除用户信息并跳转到IdentityServer的登出页面:
  注:需要配置identityserver4的登出url:
  

oidc-client.js简介

  前面的内容是基于oidc-client.js,即JavaScript版本的oidc客户端类库来实现的单页应用的,那么oidc-client.js到底为我们提供了什么功能呢?
oidc-client.js是一个支持OIDC和Oauth2.0协议的JavaScript类库,除此之外它还提供用户会话和Token的管理功能。类库中的核心类型是UserManager,它提供了用户登录、登出、用户信息管理等高层次的API,上面的例子中就是使用UserManager来完成的登录、用户信息(Access Token)获取以及登出的。
oidc-client.js或者直接说UserManager使用上需要注意以下几个方面:
  • 配置:配置的目的和asp.net core基于oidc身份验证的配置类似,主要是指明identityServer的地址、用于授权的Client信息、授权所使用的流程(授权码还是隐式流程)、授权完成后的跳转地址以及请求的Scope信息等(更多配置参数可查看文档:https://github.com/IdentityModel/oidc-client-js/wiki),如下图所示:

  

  但这里要注意的是由于以上代码对用户是可见的,所以Client的密码就省略了。
  • 方法:提供了用户管理、登录、登出、以及相关回调方法,除此之外还有会话状态查询和开启/关闭静默刷新(token)的方法。登录/登出分为三种类型:跳转、静默和弹出,具体如何使用后续介绍。

 

  • 属性:可以返回UserManager的配置、事件以及元数据服务。
  • 事件:UserManager包含了8个事件,如用户登录/登出、access token过期等:

  

oidc-client.js的各种用法

弹出式登录/登出

  弹出式登录/登出就是字面的意思,通过弹出窗口打开IdentityServer的登录/登出页面完成相应功能。
  下图为弹出式登录(仅需调用UserManager的signinPopup方法即可):
  
  注:回调页面需要使用signinPopupCallback:
  
  下图为弹出式登出:
  

静默登录与静默刷新

  静默登录和静默刷新指的就是signinSilent和startSilentRenew两个方法,而且需要注意的是startSilentRenew的原理实际上是关注了accessTokenExpiring事件,当token即将过期时调用signinSilent进行静默登录。
  静默登录方式又有两种其一是基于会话的,其二是基于刷新token,其中刷新token的优先级较高,换句话就是刷新token存在的时候,它就默认使用刷新token进行登录,刷新token比较好理解,但是会话是什么呢?它实际上就是通过IdentityServer的登录后所保持的状态,文章最开始的流程图中提到过,我们之所以可以通过授权码流程进行授权是因为登录之后有权访问IdentityServer受保护的授权终结点,从而可以获取授权码及相关Token,那这里的原理就是浏览器保存了登录状态,所以可以再次访问授权终结点来获取并刷新Token信息。
  基于会话的静默登录,下图为点击静默登录按钮后发起的请求信息,也就是正常的请求到授权码之后获取token及用户信息的过程:
  需要注意的是回调页面需要使用signinSilentCallback方法,同时不再需要页面跳转:
  
  基于刷新token的静默登录,在尝试刷新token登录之前首先需要获得刷新token,oidc中刷新token的获取是需要client支持offline_access的scope,同时在发起获取token时携带该scope:
  
  配置完成后重新登录获取token即可在存储中找到刷新token信息 :
  
  然后再次进行静默登录(相应client需要支持refresh_token的授权方式):
  
  静默登录发起的请求信息:
  
  响应信息中包含了新的token:

会话监听

  会话监听是默认开启的,在正常登录状态下,通过新的浏览器窗口从identityserver中登出(目的是清除identityserver存储在浏览器的会话信息):
  
  信息清除后它会立即尝试发起新的身份验证请求,但是返回信息中包含“需要登录”错误信息,可以在接收到相关错误信息时清除相关Token及用户信息,已达到单页应用随着IdentityServer会话结束而登出的效果:

小结

  本篇文章介绍了单页应用使用IdentityServer进行身份验证的过程及oidc-client.js JavaScript类库在应用中的使用,oidc-client.js为我们适配了oidc协议,同时还提供了丰富的功能和机制,使用这个类库可以大大减少实际工作中的开发量。
  说到单页应用的身份验证,它最根本的机制无非就是获得Access Token和Refresh Token,使用Access Token作为身份信息载体来完成身份验证,使用Refresh Token作为更新Access Token的钥匙,通过保证Access Token不过期来保证用户能够正常访问相关资源。
  但如果使用Oauth2.0协议来实现单页应用的登录(Password授权模式)会存在一些问题,首先是单页应用对于授权服务器来说是不可信的,但是Password授权由单页应用发起,属于授权服务器的用户信息及密码都要经过不可信的单页应用,这会造成一些安全问题,其次使用Oauth2.0协议完成授权后,应用与授权服务器之间实际上就没有任何关联了,授权服务器不保留用户会话,也无法实现用户登出联动的功能(即一方登出可以通知另一方),这些也正是IdentityServer4或者说OIDC协议所处理的内容。
  下篇文章,我们将继续以IdentityServer4或者说OIDC的会话管理开始,深入聊一聊它们的登录与登出。
 
参考:

从零搭建一个IdentityServer——单页应用身份验证的更多相关文章

  1. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  2. 从零搭建一个IdentityServer——目录(更新中...)

    从零搭建一个IdentityServer--项目搭建 从零搭建一个IdentityServer--集成Asp.net core Identity 从零搭建一个IdentityServer--初识Ope ...

  3. 从零搭建一个IdentityServer——会话管理与登出

    在上一篇文章中我们介绍了单页应用是如何使用IdentityServer完成身份验证的,并且在讲到静默登录以及会话监听的时候都提到会话(Session)这一概念,会话指的是用户与系统之间交互过程,反过来 ...

  4. 从零搭建一个IdentityServer——资源与访问控制

    IdentityServer作为授权服务器它的最终目的是用于对资源进行管控,这里所说的资源有两种,其一是API资源,实际上也就是OIDC协议中客户端(RP)所需要访问的一系列受保护的资源(API),授 ...

  5. 从零搭建一个IdentityServer——项目搭建

    本篇文章是基于ASP.NET CORE 5.0以及IdentityServer4的IdentityServer搭建,为什么要从零搭建呢?IdentityServer4本身就有很多模板可以直接生成一个可 ...

  6. 从零搭建一个IdentityServer——初识OpenIDConnect

    上一篇文章实现了IdentityServer4与Asp.net core Identity的集成,可以使用通过identity注册功能添加的用户,以Password的方式获取Access token, ...

  7. 从零搭建一个IdentityServer——集成Asp.net core Identity

    前面的文章使用Asp.net core 5.0以及IdentityServer4搭建了一个基础的验证服务器,并实现了基于客户端证书的Oauth2.0授权流程,以及通过access token访问被保护 ...

  8. 从零搭建一个SpringCloud项目之Feign搭建

    从零搭建一个SpringCloud项目之Feign搭建 工程简述 目的:实现trade服务通过feign调用user服务的功能.因为trade服务会用到user里的一些类和接口,所以抽出了其他服务需要 ...

  9. 单页应用 - Token 验证

    单页应用 - Token 验证 转:https://juejin.im/post/58da720b570c350058ecd40f 第一次接触单页应用,记录公司项目关于Token验证知识. Token ...

随机推荐

  1. hdu1541 Stars

    Problem Description Astronomers often examine star maps where stars are represented by points on a p ...

  2. Educational Codeforces Round 56 (Rated for Div. 2) D. Beautiful Graph (二分图染色)

    题意:有\(n\)个点,\(m\)条边的无向图,可以给每个点赋点权\({1,2,3}\),使得每个点连的奇偶不同,问有多少种方案,答案对\(998244353\)取模. 题解:要使得每个点所连的奇偶不 ...

  3. Python 装包与拆包

    装包就是把未命名的参数放到元组中,把命名参数放到字典中 a = 1, 2 print(a) (1, 2) 拆包将一个结构中的数据拆分为多个单独变量中 *args **kwargs def run1(* ...

  4. appveyor build failed --

    在 https://www.cnblogs.com/lqerio/p/11117498.html 使用了appveyor 进行 hexo 博客的版本控制持续集成. 今天push 到 github的 r ...

  5. mybaits(七)spring整合mybaits

    与 Spring 整合分析 http://www.mybatis.org/spring/zh/index.html 这里我们以传统的 Spring 为例,因为配置更直观,在 Spring 中使用配置类 ...

  6. vue中怎么动态生成form表单

    form-create 是一个可以通过 JSON 生成具有动态渲染.数据收集.验证和提交功能的表单生成组件.支持3个UI框架,并且支持生成任何 Vue 组件.内置20种常用表单组件和自定义组件,再复杂 ...

  7. Go string 一清二楚

    前言 字符串(string) 作为 go 语言的基本数据类型,在开发中必不可少,我们务必深入学习一下,做到一清二楚. 本文假设读者已经知道切片(slice)的使用,如不了解,可阅读 Go 切片 基本知 ...

  8. SwiftUI All In One

    SwiftUI All In One SwiftUI SwiftUI is an innovative, exceptionally simple way to build user interfac ...

  9. HTML 5.3

    HTML 5.3 W3C Working Draft, 18 October 2018 https://www.w3.org/TR/html53/ refs https://www.w3.org/TR ...

  10. css animation & animationend event & onanimationend

    css animation & animationend event & onanimationend https://developer.mozilla.org/en-US/docs ...