上一篇文章我们介绍了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. 【noi 2.6_4982】踩方格(DP)

    题意:一个无限大的方格矩阵,能向北.东.西三个方向走.问走N步共有多少种不同的方案. 解法: f[i]表示走 i 格的方案数. 状态转移方程推导如下--设l[i],r[i],u[i]分别为第 i 步向 ...

  2. HDU 6880 Permutation Counting dp

    题意: 给你一个n和一个长度为n-1的由0/1构成的b序列 你需要从[1,n]中构造出来一个满足b序列的序列 我们设使用[1,n]构成的序列为a,那么如果ai>ai+1,那么bi=1,否则bi= ...

  3. python+fiddler 抓取抖音数据包并下载抖音视频

    这个我们要下载视频,那么肯定首先去找抖音视频的url地址,那么这个地址肯定在json格式的数据包中,所以我们就去专门查看json格式数据包 这个怎么找我就不用了,直接看结果吧 你找json包,可以选大 ...

  4. SPOJ 227 Ordering the Soldiers

    As you are probably well aware, in Byteland it is always the military officer's main worry to order ...

  5. Codeforces #624 div3 C

    You want to perform the combo on your opponent in one popular fighting game. The combo is the string ...

  6. 郁闷的出纳员 HYSBZ - 1503

    OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的 工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资.如果他 ...

  7. k8s-1-交付dubbo微服务

    一.Dubbo微服务概述 1.1: dubbo介绍 1.2: 部署内容 二.实验环境架构 2.1: 架构 1.1 架构图解 1.最上面一排为K8S集群外服务 1.1 代码仓库使用基于git的gitee ...

  8. springmvc拦截器实现登录验证

    首先创建一个实体类: Customer: 1 package com.petcare.pojo.base; 2 3 import java.sql.Date; 4 import java.sql.Ti ...

  9. HEXO添加置顶功能

    使用库:参考 http://wangwlj.com/2018/01/09/blog_pin_post/ 目前已经有修改后支持置顶的仓库,可以直接用以下命令安装.(cmd 到博客根目录,nmp运行) $ ...

  10. C# 类 (8) - 抽象方法

    抽象 抽象方法 只能 定义在抽象类 里,并且抽象方法里没有具体的代码,像这种 为啥要定义一个空空如也的函数呢?这是为了用来约束 它的派生类 的行为, 这个例子,建立了一个数组,放了cat和dog,这两 ...