基于OIDC(OpenID Connect)的SSO

在[认证授权]系列博客中,分别对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内部维持的一个地址。访问这个地址后的信息如下:

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


原文:http://www.cnblogs.com/linianhui/p/oidc-in-action-sso.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322785.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Consul集群搭建

转载自 Consul集群搭建 概述 作为服务发现的几种产品&#xff0c;比较可以查看这里。Consul官方也提供了几种产品之间的比较&#xff0c;点击查看。 服务发现产品 Consul有很多组件&#xff0c;但总体来说&#xff0c;它是一个发现和配置服务工具&#xff0c;特性&#xff…

js遍历对象的key和value

如果想要得到数组的键值对&#xff0c;可以用以下方法 object {"name":"kejin","age":"18"}for(var index in object){console.log(index);console.log(object[index]); }

使用Identity Server 4建立Authorization Server (6) - js(angular5) 客户端

预备知识: 学习Identity Server 4的预备知识 第一部分: 使用Identity Server 4建立Authorization Server (1) 第二部分: 使用Identity Server 4建立Authorization Server (2) 第三部分: 使用Identity Server 4建立Authorization Server (3) 第四部分: 使用Identity Server 4建立…

SpringBoot整合kafka(实现producer和consumer)

转载自 SpringBoot整合kafka(实现producer和consumer) 在Windows环境下安装运行Kafka&#xff1a;https://www.jianshu.com/p/d64798e81f3b 本文代码使用的是Spring Boot 2.1.1.RELEASE 版本 <parent><groupId>org.springframework.boot</groupId><art…

ASP.NET Core 认证与授权[7]:动态授权

基于资源的授权 有些场景下&#xff0c;授权需要依赖于要访问的资源&#xff0c;例如&#xff1a;每个资源通常会有一个创建者属性&#xff0c;我们只允许该资源的创建者才可以对其进行编辑&#xff0c;删除等操作&#xff0c;这就无法通过[Authorize]特性来指定授权了。因为授…

H5的Websocket基本使用

前端代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…

springboot手动提交kafka offset

转载自 springboot手动提交kafka offset enable.auto.commit参数设置成了false 但是测试发现enable.auto.commit参数设置成了false&#xff0c;kafka的offset依然提交了&#xff08;也没有进行人工提交offset&#xff09;。 查看源码 如果我们enable.auto.commit设置为false…

可观测性与原生云监控

在近日发表的一篇文章中&#xff0c;Cindy Sridharan概括介绍了可观测性及其与原生云应用程序监控的关系。可观测性是一种理念&#xff0c;包括监控、日志聚合、指标和分布式跟踪&#xff0c;可以实时更深入地观察系统。 Sridharan的文章基于她就同一个主题所做的Velocity演讲。…

使用Microsoft.AspNetCore.TestHost进行完整的功能测试

简介 Microsoft.AspNetCore.TestHost是可以用于Asp.net Core 的功能测试工具。很多时候我们一个接口写好了&#xff0c;单元测试什么的也都ok了&#xff0c;需要完整调试一下&#xff0c;检查下单元测试未覆盖到的代码是否有bug。步骤为如下&#xff1a;程序打个断点->F5运行…

MockJs案例

有时候前端写好模板后&#xff0c;后端还完工&#xff0c;那么总不能一直让项目停滞吧&#xff0c;这里就用Mockjs来模拟后端接口的数据&#xff0c;让我们先人一步完成项目。 首先创建一个html&#xff0c;导入axios和mockjs 再用mock去拦截请求&#xff0c;如果后端接口写好了…

Entity Framework Core 使用HiLo生成主键

HiLo是在NHibernate中生成主键的一种方式&#xff0c;不过现在我们可以在Entity Framework Core中使用。所以在这篇内容中&#xff0c;我将向您在介绍如何在Entity Framework Core中使用HiLo生成主键。 什么是Hilo&#xff1f; HiLo是High Low的简写&#xff0c;翻译成中文叫高…

Echarts报错:Component series.lines not exists. Load it first.

前几天用的echarts标签是bootcdn的 <script src"https://cdn.bootcdn.net/ajax/libs/echarts/4.7.0/echarts-en.common.js"></script>用着官方给的案例还可以&#xff0c;但是一用gallery社区里面的例子就报错 后来经过不断调试终于知道是需要换个cdn&…

认识微软Visual Studio Tools for AI

微软已经发布了其 Visual Studio Tools for AI 的测试版本&#xff0c;这是微软 Visual Studio 2017 IDE 的扩展&#xff0c;可以让开发人员和数据科学家将深度学习模型嵌入到应用程序中。Visual Studio Tools for AI 工具同时支持 Microsoft 的 Cognitive Toolkit 和 Google 的…

mybatis源码阅读(一):SqlSession和SqlSessionFactory

转载自 mybatis源码阅读(一)&#xff1a;SqlSession和SqlSessionFactory 一、接口定义 听名字就知道这里使用了工厂方法模式&#xff0c;SqlSessionFactory负责创建SqlSession对象。其中开发人员最常用的就是DefaultSqlSession &#xff08;1&#xff09;SqlSession接口定义…

开源纯C#工控网关+组态软件(六)图元组件

一、 图元概述 图元是构成人机界面的基本单元。如一个个的电机、设备、数据显示、仪表盘&#xff0c;都是图元。构建人机界面的过程就是铺排、挪移、定位图元的过程。 图元设计是绘图和编码的结合。因为图元不仅有显示和动画&#xff0c;还有背后操纵动画的控制逻辑。 一个好…

git合并分支的策略(赞)

假设当前有两个分支 master和test&#xff0c;两个分支一模一样&#xff0c;都有这三个文件 现在test添加一个test4.txt&#xff0c;然后提交到本地&#xff08;git add . git commit&#xff09; 切换到master分支上&#xff0c;git checkout master git merge test 这样mase…

改造独立部署(SCD)模式下.NET Core应用程序 dotnet的exe文件启动过程

设置一个小目标 改造前 改造后 独立部署SCD模式&#xff0c;是指在使用dotnet publish 命令时带上-r 参数运行时标识符&#xff08;RID&#xff09;。 目标提出原因&#xff1a;SCD模式下文件太乱了&#xff0c;很多文件在开发时大多又涉及不到&#xff0c;发布后如果能把文件…

mybatis源码阅读(四):mapper(dao)实例化

转载自 mybatis源码阅读(四)&#xff1a;mapper(dao)实例化 在开始分析之前&#xff0c;先来了解一下这个模块中的核心组件之间的关系&#xff0c;如图&#xff1a; 1.MapperRegistry&MapperProxyFactory MapperRegistry是Mapper接口及其对应的代理对象工程的注册中心&…

自定义路由匹配和生成

前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用&#xff1a; 《ASP.NET Core 中的SEO优化&#xff08;1&#xff09;&#xff1a;中间件实现服务端静态化缓存》 《ASP.NET Core 中的SEO优化&#xff08;2&#xff09;&#xff1a;中间件中渲染Razor视图》…

如何封装并发布一个属于自己的ui组件库

以前就一直有个想法自己能不能封装一个类似于elementui一样的组件库&#xff0c;然后发布到npm上去&#xff0c;毕竟前端说白了&#xff0c;将组件v上去&#xff0c;然后进行数据交互。借助这次端午&#xff0c;终于有机会&#xff0c;尝试自己去封装发布组件库了 我这里了只做…