Shibboleth

Stella981
• 阅读 896

背景

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这