[认证授权]系列博客中,分别对OAuth2和OIDC在理论概念方面进行了解释说明,其间虽然我有写过一个完整的示例(https://github.com/linianhui/oidc.example),但是却没有在实践方面做出过解释。在这里新开一个系列博客,来解释其各种不同的应用场景。因为OIDC是在OAuth2之上的协议,所以这其中也会包含OAuth2的一些内容。

OIDC协议本身有很多的开源实现,这里选取的是基于.Net的开源实现基于IdentityServer4。本系列的源代码位于https://github.com/linianhui/oidc.example。clone下来后用管理员身份运行build.ps1来部署整个系统,其中可能会弹出UAC警告(脚本会修改host文件,记得允许管理员读写这个文件先)。部署完后的样子如下:

本文中主要是关注一下SSO这部分的内容,主要是跨一级域单点登录统一登出功能。其中涉及到的站点有一下4个:

  1. oidc-server.dev:利用oidc实现的统一认证和授权中心,SSO站点。
  2. oidc-client-hybrid.dev:oidc的一个客户端,采用hybrid模式。
  3. oidc-client-implicit.dev:odic的另一个客户端,采用implicit模式。
  4. oidc-client-js.dev:oidc的又一个客户端,采用implicit模式,纯静态网站,只有js和html,无服务端代码。

单点登录

通常来讲,SSO包括统一的登录统一的登出这两部分。基于OIDC实现的SSO主要是利用OIDC服务作为用户认证中心作为统一入口,使得所有的需要登录的地方都交给OIDC服务来做。更直白点说就是把需要进行用户认证的客户端中的用户认证这部分都剥离出来交给OIDC认证中心来做。具体的交互流程如下:

其中这三个客户端是完全独立的位于不同的域名之下,且没有任何依赖关系,三者均依赖oidc-server.dev这个站点进行认证和授权,通信协议为HTTP,那么下面则通过它们之间的HTTP消息来解释其具体的流程。这个过程中使用fiddler来进行监视其所有的请求。

第1步:OIDC-Client- 触发认证请求

在浏览器打开oidc-client-implicit.dev这个站点,打开后如下(QQ这个先不管它,后面单独介绍)。

点击Oidc Login后,会触发一个302的重定向操作。具体的HTTP请求和响应信息如下:

Request:Get后面的URL是我们点击Oidc Login的Url,这个URL包含一个参数,代表登录成功后所要回到的页面是哪里。

Response:服务器返回了一个302重定向。

  1. Location的Url指向了oidc-server.dev这个站点,其中还携带了一大堆参数(参数后面一小节介绍);
  2. Set-Cookie设置了一个nonce的cookie,主要目的用于安全方面。

第2步:OIDC-Client - 认证请求

紧接上一步,浏览器在接收到第1步的302响应后,会对Location所指定的URL发起一个Get请求。这个请求携带的参数如下:

其中参数的含义在OIDC的认证请求有详细的解释(注:其中采用的认证类型不管是authorization code,或者implict,还是hybrid都无关紧要,它们的区别只是其适用场景的差异,并不影响整个流程)。

  1. client_id=implicit-client:发起认证请求的客户端的唯一标识,这个客户端事先已经在oidc-server.dev这个站点注册过了。
  2. reponse_mode=form_post:指示oidc服务器应该使用form表单的形式返回数据给客户端。
  3. response_type=id_token:区别于oauth2授权请求的一点,必须包含有id_token这一项。
  4. scope=openid profile:区别于oauth2授权请求的一点,必须包含有openid这一项。
  5. state:oauth2定义的一个状态字符串,这里的实现是加密保存了一些客户端的状态信息(用于记录客户端的一些状态,在登录成功后会有用处),oidc会在认证完成后原样返回这个参数。
  6. nonce:上一步中写入cookie的值,这字符串将来会包含在idtoken中原样返回给客户端。
  7. redirect_uri:认证成功后的回调地址,oidc-server.dev会把认证的信息发送给这个地址。

第3步:OIDC-Server - 验证请求信息

oidc-server.dev站点会验证第2步中传递过来的信息,比如client_id是否有效,redircet_uri是否合法,其他的参数是否合法之类的验证。如果验证通过,则会进行下一步操作。

第4步:OIDC-Server - 打开登录页面

在oidc-server.dev站点验证完成后,如果没有从来没有客户端通过oidc-server.dev登陆过,那么第2步的请求会返回一个302重定向重定向到登录页面如果是已经登录,则会直接返回第5步中生产重定向地址

浏览器会打开响应消息中Location指定的地址(登录页面)。如下:

第5步:OIDC-Server - 完成用户登录,同时记录登录状态

在第四步输入账户密码点击提交后,会POST如下信息到服务器端。

服务器验证用户的账号密码,通过后会使用Set-cookie维持自身的登录状态。然后使用302重定向到下一个页面。

第6步:浏览器 - 打开上一步重定向的地址,同时自动发起一个post请求

form的地址是在第2步中设置的回调地址,form表单中包含(根据具体的认证方式authorization code,implict或者hybrid,其包含的信息会有一些差异,这个例子中是采用的implicit方式)如下信息:

  1. id_token:id_token即为认证的信息,OIDC的核心部分,采用JWT格式包装的一个字符串。
  2. scope:用户允许访问的scope信息。
  3. state:第1步中发送的state,原样返回。
  4. session_state:会话状态。

id_token包含的具体的信息如下:

其中包含认证的服务器信息iss,客户端的信息aud,时效信息nbf和exp,用户信息sub和nickname,会话信息sid,以及第1步中设置的nonce。还有其签名的信息alg=RS256,表示idtoken最后的一段信息(上图中浅蓝色的部分)是oidc-server.dev使用RSA256对id_token的header和payload部分所生产的数字签名。客户端需要使用oidc-server.dev提供的公钥来验证这个数字签名

第7步:OIDC-Client - 接收第6步POST过来的参数,构建自身的登录状态

客户端验证id_token的有效性,其中验证所需的公钥来自OIDC的发现服务中的jwk_uri,这个验证是必须的,目的时为了保证客户端得到的id_token是oidc-sercer.dev颁发的,并且没有被篡改过,以及id_token的有效时间验证。数字签名的JWT可以保证id_token的不可否认性,认证和完整性,但是并不能保证其机密性,所以id_token中千万不要包含有机密性要求的敏感的数据。如果确实需要包含,则需要对其进行加密处理(比如JWE规范)。其中验证也包含对nonce(包含在id_token中)的验证(第1步设置的名为nonce的cookie)。

在验证完成后,客户端就可以取出来其中包含的用户信息来构建自身的登录状态,比如上如中Set-Cookie=lnh.oidc这个cookie。然后清除第1步中设置的名为nonce的cookie。

最后跳转到客户端指定的地址(这个地址信息被保存在第1步中传递给oidc-server.dev的state参数中,被oidc-server.dev原样返回了这个信息)。然后读取用户信息如下(这里读取的是id_token中的完整信息):

其他的客户端登录

登录流程是和上面的步骤是一样的,一样会发起认证请求,区别在于已经登录的时候会在第4步直接返回post信息给客户端的地址,而不是打开一个登录页面,这里就不再详细介绍了。大家可以在本地运行一下,通过fiddler观察一下它们的请求流程。贴一下oidc-client-hybrid.dev这个客户端登录后的页面吧:

统一退出

退出的流程相比登录简单一些。如下图:

其中核心部分在于利用浏览器作为中间的媒介,来逐一的通知已经登录的客户端退出登录。

第1步:OIDC-Client - 触发登出请求

点击Logout链接。

点击退出后会触发一个GET请求,如下:

上图这个请求会返回一个302的响应,Location的地址指向oidc-server.dev的一个endsession的接口。同时会通过Set-Cookie来清除自身的cookie。

第2步:OIDC-Client - 登出请求

浏览器通过GET访问上一步中指定的Location地址。

接口地址定义在OIDC的发现服务中的end_session_endpoint字段中,参数信息定义在OIDC的Front-Channel-Logout规范中。

第3步:OIDC-Server - 验证登出请求

验证上图中传递的信息,如果信息无误则再一次重定向到一个地址(这里是IdentityServer4的实现机制,其实可以无需这个再次重定向的)。

第4步:OIDC-Server - 登出自身,返回包含IFrame的HTML

浏览器打开第3步中重定向的地址:

响应中会通过Set-Cookie(idsrv和idsrv.session)清除oidc-server.dev自身的登录状态。然后包含一个HTML表单页面,上图中iframe指向的地址是IdentityServer4内部维持的一个地址。访问这个地址后的信息如下:

 <!DOCTYPE html>
<html>
<style>iframe{display:none;width:0;height:0;}</style>
<body>
<iframe src='http://oidc-client-implicit.dev/oidc/front-channel-logout-callback?sid=b51ea235574807beb0deff7c6db6a381&iss=http%3A%2F%2Foidc-server.dev'></iframe>
<iframe src='http://oidc-client-hybrid.dev/oidc/front-channel-logout-callback?sid=b51ea235574807beb0deff7c6db6a381&iss=http%3A%2F%2Foidc-server.dev'></iframe>
</body>
</html>

上面代码中的iframe是真正的调用已经登录的客户端进行登出的地址(IdentityServer4会记录下来已经登录的客户端,没有登陆过的和没有配置启用Front-Channel-Logout的则不会出现在这里)。其中iframe指向的地址是OIDC客户端在oidc-server.dev中注册的时候配置的地址。参数则是动态附加上去的参数。

最后页面中包含一个js脚本文件,在页面load完成后,跳转到第2步中指定的post_logout_redirect_uri指向的回调页面。

第5步:OIDC-Client - 处理登出回调通知

在浏览器访问上面代码中iframe指向的地址的时候,被动登出的OIDC客户端会接收到登出通知。

响应中通过Set-Cookie(lnh.oidc)清除了需要被动登出的客户端的Cookie。至此,统一的登出完成。

总结

本文介绍了基于OIDC实现的SSO的工作原理和流程,但并未涉及到OIDC的具体实现IdentityServer4的是如何使用的(这部分通过读我提供的源码应该是很容易理解的),旨在解释一下如何用OIDC实现SSO,而非如何使用OIDC的某一个实现框架。OIDC是一个协议族,这些具体每一步怎么做都是有标准的规范的,所以侧重在了用HTTP来描述这个过程,这样这个流程也就可以用在java,php,nodejs等等开发平台上。

参考

本文源代码:https://github.com/linianhui/oidc.example

认证授权:http://www.cnblogs.com/linianhui/category/929878.html

Id Token:http://www.cnblogs.com/linianhui/p/openid-connect-core.html#auto_id_5

JWT:http://www.cnblogs.com/linianhui/p/oauth2-extensions-protocol-and-json-web-token.html#auto_id_5

数字签名:http://www.cnblogs.com/linianhui/p/security-based-toolbox.html#auto_id_16

OIDC:http://openid.net/connect/

IdentityServer4:https://github.com/IdentityServer/IdentityServer4

[OIDC in Action] 1. 基于OIDC(OpenID Connect)的SSO的更多相关文章

  1. [OIDC in Action] 3. 基于OIDC(OpenID Connect)的SSO(添加Github OAuth 2.0的支持)

    在上上一篇基于OIDC的SSO的登录页面的截图中有出现QQ登录的地方.这个其实是通过扩展OIDC的OpenID Provider来实现的,OpenID Provider简称OP,OP是OIDC的一个很 ...

  2. [OIDC in Action] 2. 基于OIDC(OpenID Connect)的SSO(纯JS客户端)

    在上一篇基于OIDC的SSO的中涉及到了4个Web站点: oidc-server.dev:利用oidc实现的统一认证和授权中心,SSO站点. oidc-client-hybrid.dev:oidc的一 ...

  3. [认证授权] 5.OIDC(OpenId Connect)身份认证授权(扩展部分)

    在上一篇[认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)中解释了OIDC的核心部分的功能,即OIDC如何提供id token来用于认证.由于OIDC是一个协议族,如果 ...

  4. [认证授权] 5.OIDC(OpenId Connect)身份认证(扩展部分)

    在上一篇[认证授权] 4.OIDC(OpenId Connect)身份认证(核心部分)中解释了OIDC的核心部分的功能,即OIDC如何提供id token来用于认证.由于OIDC是一个协议族,如果只是 ...

  5. [认证授权] 4.OIDC(OpenId Connect)身份认证授权(核心部分)

    0 目录 认证授权系列:http://www.cnblogs.com/linianhui/category/929878.html 1 什么是OIDC? 看一下官方的介绍(http://openid. ...

  6. [认证授权] 4.OIDC(OpenId Connect)身份认证(核心部分)

    1 什么是OIDC? 看一下官方的介绍(http://openid.net/connect/): OpenID Connect 1.0 is a simple identity layer on to ...

  7. IdentityServer4 中文文档 -11- (快速入门)添加基于 OpenID Connect 的用户认证

    IdentityServer4 中文文档 -11- (快速入门)添加基于 OpenID Connect 的用户认证 原文:http://docs.identityserver.io/en/releas ...

  8. ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

    在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...

  9. ASP.NET Core的身份认证框架IdentityServer4(9)-使用OpenID Connect添加用户认证

    OpenID Connect OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层. 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似R ...

随机推荐

  1. Cygwin-添加到右键菜单脚本--一键安装、卸载

    平时习惯用一些linux命令来完成工作,在Windows上有cygwin和gitbash两个选择.这两个我都装了. 相对来说cygwin支持的功能更多一些,但是它没有默认绑定到右键菜单.为此,我想到用 ...

  2. vmware虚拟机安装CentOS-6.5教程

    linux是企业最常用的服务器系统之一,CentOS是免费的,所以用的企业也挺多,今天给大家分享怎么在自己电脑的虚拟机中安装CentOS-6.5,以便用来玩耍,没事的时候可以学学linux的一些知识. ...

  3. banner幻灯片

    emmm本来想用js写的,但是感觉难就换了jquery. 1.把图片的位置.透明度.宽高.z-index信息用数组datas保存起来,移动的时候直接把位置等信息赋值给li. 2.点击next,即显示下 ...

  4. 如何才能通俗易懂的解释javascript里面的"闭包"?

    看了知乎上的话题 如何才能通俗易懂的解释javascript里面的‘闭包’?,受到一些启发,因此结合实例将回答中几个精要的答案做一个简单的分析以便加深理解. 1. "闭包就是跨作用域访问变量 ...

  5. JDBC工具类实例

    本文以讲解用单利模式实现一个简单的JDBC实用工具类JDBC连接的四个基本步骤:1.加载相应数据库驱动2.建立相应数据库连接3.构建Statement语句,即增删改查SQL语句4.执行Statemen ...

  6. ClassLoader 工作机制

    ClassLoader 采用上级委托接待机制加载 class JVM 平台提供三层 ClassLoader 1.Bootstrap ClassLoader:主要加载 JVM 自身工作需要的类 2.Ex ...

  7. MySQL数据库主从复制实践

        MySQL 主从(MySQL Replication),主要用于 MySQL 的实时备份.高可用HA.读写分离.在配置主从复制之前需要先准备 2 台 MySQL 服务器. 一.MySQL主从原 ...

  8. TinyOS编程思想和Nesc基础语法

    TinyOS操作系统由nesc语言写成,从程序员角度看,它的基本作用就是提供了一组API接口以及一些编程规则. 具体来说,基于nesc语言的TinyOS编程行为具有以下特点: a.兼容C语言:使用ne ...

  9. 读书笔记-你不知道的JS上-函数作用域与块作用域

    函数作用域 Javascript具有基于函数的作用域,每声明一个函数,都会产生一个对应的作用域. //全局作用域包含f1 function f1(a) { var b = 1; //f1作用域包含a, ...

  10. 纯js实现DIV拖拽

    写代码的时候遇到需要对绝对布局的div进行拖拽的功能,起初为了省事直接在网上扒拉了一番,看到大神张鑫旭的一篇文章<JavaScript实现最简单的拖拽效果>,便直接拿来使用(膜拜大神).但 ...