Shibboleth-IdP 的 OAuth2 对接方案详解

背景

Shibboleth 是一个支持 SAML2.0 的开源 IdP 服务器。

SAML2.0 是一个联邦式认证的标准,简单来说就是能够让应用方——也就是资源提供者(Service Provider,简称 SP)与任意的机构内部认证——也就是身份提供者(Identity Provider)对接时,能够均采用相同的协议标准。这显然能够简化集成,像 AWS,Azure 等云服务商都支持 SAML2.0 方式的机构账号对接。同时这种简化也促进了资源的共享,并形成了各式各样的身份联盟,比如中国的 CARSI[1],澳大利亚的 AAF[2],瑞士的 SWITCHaai[3] 等等,并且通过 eduGAIN[4] 将这些联盟连接起来。

Shibboleth 除了支持 SAML 以外,他在 IdP3 开始支持 CAS 协议[5],并且计划在 IdP4 开始引入 OpenID Connect ,然后在 IdP5 开始稳定支持 OpenID Connect 说实话这是一个槽点(另一个槽点是 CAS 也开始支持 SAML,你说这两拨人真是。。。)。然而如果我们要提供 CAS/OAuth2/OpenID Connect 服务的话,Shibboleth 显然不是优先的选项,SAML2.0 才是选择他的目的。好在 Shibboleth 支持通过一些外部插件的模式来进行认证[6],而 Unicon/shib-cas-authn3[7]是一个集成 CAS[8] 和 Shibboleth 的插件。OAuth2 的集成即基于此插件修改实现。

插件版本

由于 Shibboleth IdP 在 3.4.3 之后修改了一个内部的 API 实现,因此插件版本割裂为 3.3.0 和 3.2.3 两个版本。3.3.0 插件仅支持 Shibboleth IdP 3.4.6,而 3.2.3 仅支持 IdP 3.4.3。OAuth2 插件基于 shib-cas-authn3 插件的 3.2.3 版本修改,暂时不支持 IdP 3.4.6。

修改思路

OAuth2 本身只是授权协议,但是通常我们会将其与认证结合使用。因此包含了认证的 OAuth2 Server 可以与 CAS Server 进行对比。先看流程部分

步骤 CAS OAuth2 差异
1 回调到 CAS 认证 请求授权码,通常回调至认证 相对一致
2 认证完成,获得 ticket 认证完成,获得 code 相对一致
3 code 更换 token OAuth 独有
4 校验 ticket 并获取用户属性 使用 token 调用用户属性接口 相对一致
5 refresh token 可以刷新 token OAuth 独有

可以看到,不考虑 refresh token,把 OAuth 的第二步和第三步连接起来,OAuth 在流程上是可以和 CAS 保持一致的,这意味着插件可以不用进行伤筋动骨的修改,只需要针对性的微调即可。

其他差异:

  • redirect_uri 校验
  • 用户名的判断
  • 属性接口的标准

与 CAS 不同,OAuth2 客户端在使用 code 更换 token 时,还需要附带自己的 redirect_uri,并且 OAuth2 服务端根据标准应该要检验两个 redirect_uri 是否一致。而 Shibboleth IdP 会根据 uri 中的 conversation=e1s1 来区分会话,比如 conversation=e1s1conversation=e1s2 。因此在集成中,IdP 的 redirect_uri 必须动态判定的,不能和 CAS 服务一样静态的指定 /cas/login 来解决。

另一个问题是用户名,对于 CAS 协议而言,用户名是一个标准的字段,他和属性的释放是区分开的。例如这个示例里,用户名已经由 <cas:user>字段标记出来,属性则包含在<cas:attributes>下面:

<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
   <cas:authenticationSuccess>
     <cas:user>username</cas:user>
     <cas:attributes>
       <cas:firstname>John</cas:firstname>
       <cas:lastname>Doe</cas:lastname>
       <cas:title>Mr.</cas:title>
       <cas:email>jdoe@example.org</cas:email>
       <cas:affiliation>staff</cas:affiliation>
       <cas:affiliation>faculty</cas:affiliation>
     </cas:attributes>
     <cas:proxyGrantingTicket>PGTIOU-84678-8a9d...</cas:proxyGrantingTicket>
   </cas:authenticationSuccess>
 </cas:serviceResponse>

而 CAS 插件会讲用户名作为 principalname 传递给 shibboleth,因此修改后的 OAuth2 必须也找到一个唯一确定的用户名字段来作为 principalname,这样才能复用原本 CAS 插件的很多功能。

因此这就引出了第三个问题,属性接口的标准。这实质上是 CAS 协议和 OAuth2 协议的核心分歧。OAuth2 本质上是一个授权协议,他的所有规范都是针对授权过程的(怎么获取 token )对于资源接口没有规定。而 CAS 是一个认证协议,他在认证返回的属性上有很明确的规范。因此这里,我们必须人为的给 OAuth2 Server 返回人员属性的接口进行规定。而这个规定实际上就可以直接参照 OpenID-Connect[9] 内关于 userinfo endpoint 的规范。例如这样的一个返回中,sub 是必须存在的字段,作用类似于 CAS 协议中的 <cas:user>,其他则是可选的,类似于 CAS 协议中 <cas:attributes> 下层中的那些属性

  HTTP/1.1 200 OK
  Content-Type: application/json

  {
   "sub": "248289761001",
   "name": "Jane Doe",
   "given_name": "Jane",
   "family_name": "Doe",
   "preferred_username": "j.doe",
   "email": "janedoe@example.com",
   "picture": "http://example.com/janedoe/me.jpg"
  }

当然这只是一个建议 。在插件中,我们通过配置 shibcas.oauth2principalname = sub 来指定 principalname 所指代的属性字段 ,由用户来选择。接口必须避免层级嵌套,以确保插件能够直接的获取到对应的属性。

最终的 OAuth2 插件源码地址在 https://github.com/shanghai-edu/shib-cas-authn3,选择 tag 3.2.4-oauth 。插件代码基于北京大学赖清楠老师的版本进一步修改优化,特别感谢北京大学 CARSI 项目组团队的前期工作。

插件安装文档详见 CARSI-WiKiSEAC-Document

3.3.0 的 OAuth 版本修改正在工作中,To Be Continued ~~~

实践

常见的坑

实际上正是由于 OAuth2 缺乏 userinfo 的规范,导致 OAuth2 协议对接时,通常需要少量的代码层定制。这反过来导致了一些开发商在提供 OAuth2 产品时的随意和不规范。以下是我碰到过的几个反面例子:

  • Token endpoint 不支持 POST 请求
    OAuth2 的 RFC[10] 明确的要求客户端在请求 token endpoint 创建 token 时应该采用 POST 方法,并将请求参数以 application/x-www-form-urlencoded 编码放在 body 内传输。这很自然,POST 创建资源嘛。但实际上我们实现的往往会选择支持 GET 请求,因为这样会更容易调试,虽然这样就很不 REST 了。小米[11],微信[12] 等大厂的开放平台,也均提供 GET 方式的接口和文档,这显然是出于调试方便的考虑。然而多支持一个 GET 模式,和只支持 GET 显然不是一回事。。。总不能把老实遵从 RFC 的客户端给拒绝了对吧。
  • 注册 redirect_uri 的校验过于严格
    redirect_uri 当然是要校验的。但通常而言,校验到域名,或者校验到 url 路径足矣。要求 url 参数也完全一致的,那其实就根本没在好好校验了,因为这就是在做简单的字符串匹配。在很多时候我们还需要一些动态的参数来提供一些回调页面的个性化支持,这也是 RFC 所允许的。
  • userinfo 接口过于复杂
    OAuth2 是一个授权规范,他对接口设计没有规定,当然可以任意的来发挥。但是当我们把 OAuth2 用于认证时,至少应该在反应用户基本属性——即 userinfo 这个接口上,尽量的简化。建议参照 openid-connect 关于 userinfo 的规范来实现这个接口。

快速测试

oauth-server-lite

oauth-server-lite[13] 是一个轻量级的 OAuth2 服务器,认证部分对接 LDAP ,并将 LDAP 的属性映射为 userinfo 接口。因此可用于 IdP 的 OAuth2 对接测试。

oauth-server-lite 的认证部分支持验证码和IP地址封禁,以对抗暴力破解。因此也将其直接和 IdP 打包在一起部署,作为 IdP 的安全加固手段之一应用。

oauth-server-lite 的 /oauth/v1/userinfo 接口实现了 OpenID-Connect 的规范,它会将用户名作为 sub 字段默认插入,并将 ldap 返回的属性作为其他字段输出。ldap 的多值部分以 ; 连接为字符串,例如:

{
  "cn": "小冯冯", 
  "uid": "11116666", 
  "memberOf": "教职工", 
  "mail": "qfeng@exampe.org", 
  "sub": "11116666"
}

oauth-server-lite 的事情,留到下回再说吧

以上

参考文献

[1] CERNET Authentication and Resource Sharing Infrastructure
[2] Australian Access Federation
[3] SWITCH Authentication and Authorization Infrastructure
[4] eduGAIN
[5] Shibboleth Implemented Protocols and Profiles
[6] Shibboleth RemoteUserAuthnConfiguration
[7] A Shibboleth IdP v3.X plugin for authentication via an external CAS Server
[8] CAS Enterprise Single Sign-On
[9] OpenID Connect Core 1.0 incorporating errata set 1
[10] OAuth2 RFC
[11] 小米开发平台
[12] 微信开发平台
[13] shanghai-edu/oauth-server-lite

转载授权

CC BY-SA

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266