从零搭建一个IdentityServer——单页应用身份验证

  上一篇文章咱们介绍了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
  一、新建一个asp.net core的web应用程序:
  二、添加oidc的js组件:
  oidc的js客户端库: https://github.com/IdentityModel/oidc-client-js
  能够经过npm进行安装或者在Github上直接下载,如下为npm安装方法:
  npm i oidc-client
  
  完成以后在相关目录下可找到oidc-client相关文件:
    
  将其复制到适合的位置便可。
  三、经过数据库添加一个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;
  参考以下,内存实例:
  
  数据库数据:
  
  四、添加基于oidc-client的登陆、API访问以及登出业务逻辑代码App.js:
  UserManager对象初始化:
  
  使用UserManager实例进行登陆跳转:
  
  使用UserManager实例获取用户信息,而后经过用户信息中的access token访问受保护资源:
  
  使用UserManager实例进行登出跳转:
   
  五、添加功能页面Index.html,包含登陆、API访问及登出功能:
  
  六、添加用于处理受权码的重定向页面:
  
  到此单页应用程序已经建立完毕,后面就使用该程序要介绍它是如何完成身份验证,并访问受保护资源的。

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

  咱们使用一个简单的asp.net core web api项目(本系列文章用过的)来进行演示,它对于普通API项目来讲要点在于:
  一、添加基于JwtBearer的身份验证处理器:
  
  二、添加跨域处理,添加跨域策略配置:
  
  三、在asp.net core应用请求管道中应用跨域配置:
  
  四、受保护内容经过authorize特性进行标记:
  
  一切准备就绪后运行三个应用程序,单页应用运行并打开index.html页面效果以下,一共有三个功能,登陆、调用API以及登出:
  
  登陆:它其实是调用oidc Client的signinRedirect方法,语义上来讲它是经过重定向的方式进行登陆,而它实际执行的效果以下:
  跳转到了IdentityServer的登陆页面,而后咱们再看看它本质上是作了什么?
  它其实是发起了一个受权码流程的身份验证请求(请求过程可参考: http://www.javashuo.com/article/p-mqfghsbm-vd.html),发起请求后,因为当前用户没有在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过时等:

  

  以上内容参考文档: https://github.com/IdentityModel/oidc-client-js/wiki

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的会话管理开始,深刻聊一聊它们的登陆与登出。
 
参考:
相关文章
相关标签/搜索