前言
这还是一个标题党。
OAuth2 现在已经是开放授权协议的事实标准,你可以看到几乎所有的 xxx 开放平台均采取的 OAuth2 协议来进行授权。而在 Authorization Code 模式的基础上结合 JWT ,标准化的 userinfo endpoint 和服务发现,就成了 OpenID-Connect。当然即便不加上这些限定,OAuth2 在 Authorization Code 模式下,也通常被用作统一身份认证的解决方案提供给用户。而由于 OAuth2 与 CAS 不同,缺乏 Apereo CAS 这样的重量级产品来对标(话说回来,Apereo CAS 现在自己就支持 OAuth2 来着)。于是市场上的 OAuth2 实现可谓群魔乱舞,槽点一时难以穷尽。
个人建议,在选取 OAuth2 产品时,务必通过 oauth playground 进行测试,以验证协议实现的标准性,避免将来踩坑。而如果需要自己临时起一个 OAuth2 服务做测试的话嘛~~~
是的,搭一个 OAuth 服务器 5 分钟就够了。
Apereo CAS - OAuth2
是的,Apereo CAS 现在也支持 OAuth2 协议了,所以如果你已经按照 15 分钟部署一个 CAS 服务并对接 Shibboleth-IdP 3.4.6 的路程部署好了 CAS 服务的话,那么只需要略微的调整,就可以让他支持 OAuth2 了,5 分钟足矣。
下文假定已经安装好了 shibboleth-idp-3.4.6 和 cas 6.1 ,并使用 httpd 方式代理发布。
修改 build.gradle
,在 dependencies
内进一步增加 oauth-webflow
的编译依赖
dependencies {
// Other CAS dependencies/modules may be listed here...
compile "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
compile "org.apereo.cas:cas-server-support-ldap:${casServerVersion}"
compile "org.apereo.cas:cas-server-support-oauth-webflow:${project.'cas.version'}"
}
复制代码
在 cas-overlay-template/etc/cas/services/
目录内新增 oauth-1000.json
文件,注册我们的 oauth
服务
{
@class : org.apereo.cas.support.oauth.services.OAuthRegisteredService
clientId: google
clientSecret: google
serviceId: ^https://developers.google.com/.*
name: OAuthService
id: 1000
supportedGrantTypes: [ "java.util.HashSet", [ "authorization_code" ] ]
supportedResponseTypes: [ "java.util.HashSet", [ "code" ] ]
}
复制代码
设置好 clientId
,clientSecret
,设置好允许的 serviceId
,也就是 redirect_uri
。
修改 cas-overlay-template/etc/cas/config/cas.properties
,增加这条配置。
cas.authn.oauth.userProfileViewType=FLAT
复制代码
此处默认的值是 NEST
,此时 userinfo
的中,属性部分是带 attributes
的子结构的, 如果是 FLAT
的话,则会去掉结构铺平。以下示例引用自 Apereo CAS 官网
# Nested
{
"id": "casuser",
"attributes": {
"email": "casuser@example.org",
"name": "CAS"
},
"something": "else"
}
# FLAT
{
"id": "casuser",
"email": "casuser@example.org",
"name": "CAS",
"something": "else"
}
复制代码
Shibboleth-IdP 的 OAuth2 对接需要去掉结构,所以我们使用 FLAT
模式
好啦,重新编译 docker
并运行,OAuth2 服务已经搞定了,去 oauth playground 测试一下吧。
Endpoints
Authorization endpoint : /cas/oauth2.0/authorize
Token endpoint : /cas/oauth2.0/accessToken
Userinfo endpoint : /cas/oauth2.0/profile
oauth-server-lite
如果没有 CAS 部署的前序工作的话,5 分钟的时间可能不太够。但是既然 FLAG
已经立好了。。。我们可以用 oauth-server-lite 来快速的构建一个 oauth
服务。
先装个 redis,这是唯一的依赖了
$ yum install epel-release
$ yum install redis
$ systemctl start redis
$ systemctl enable redis
复制代码
下载 oauth-server-lite
并解压。他是 go
语言写的,改下配置直接就可以跑了
$ mkdir oauth-server-lite
$ cd oauth-server-lite
$ wget https://github.com/shanghai-edu/oauth-server-lite/releases/download/v0.3.0/oauth-server-lite-0.3.tar.gz
$ tar -zxvf oauth-server-lite-0.3.tar.gz
$ mv cfg.example.json cfg.json
复制代码
修改配置文件
{
"log_level": "info", # info/warn/debug 三种
"db": {
"sqlite":"sqlite.db", # 只要不为空,则使用 sqlite 模式,存储到字段中的 sqlite 文件中
"mysql": "root:password@tcp(127.0.0.1:3306)/oauth?charset=utf8&parseTime=True&loc=Local", # 使用 mysql 模式时的数据库连接参数
"db_debug": false # true 时会输出详细的 sql debug
},
"redis": {
"dsn": "127.0.0.1:6379",
"max_idle": 5,
"conn_timeout": 5, # 单位都是秒
"read_timeout": 5,
"write_timeout": 5,
"password": ""
},
"redis_namespace":{ # redis key 的命名空间,保持默认即可
"oauth":"oauth:",
"cache":"cache:",
"lock":"lock:",
"fail":"fail:"
},
"ldap": {
"addr": "ldap.example.org:389",
"baseDn": "dc=example,dc=org",
"bindDn": "cn=manager,dc=example,dc=org",
"bindPass": "password",
"authFilter": "(&(uid=%s))",
"attributes": ["uid", "cn", "mail"], # ldap 返回的属性。这部分会映射为 userinfo 的接口。如果留空则返回全部属性
"tls": false,
"startTLS": false
},
"http": {
"listen": "0.0.0.0:18080",
"manage_ip": ["127.0.0.1"], # 管理接口的授信 ip
"x-api-key": "shanghai-edu", # 管理接口的 api key
"session_options":{ # session 参数
"path":"/",
"domain":"idp.example.org", # 必须与实际域名匹配
"max_age":7200,
"secure":false,
"http_only":false
},
"max_multipart_memory":100
},
"max_failed":5, # 最大密码错误次数
"failed_intervel":300, # 密码错误统计的间隔时间
"lock_time":600, # 锁定时间
"access_token_expired":7200, # oauth access token 有效期,单位是秒
"old_access_token_expired":300, # 新的 oauth access token 生成时,老 token 的保留时间
"refresh_token_expired_day":365, # refresh token 的有效期,单位是天
"code_expired":300 # authorization_code 的有效期,单位是秒
}
复制代码
配置文件的示例。考虑到 5 分钟还是有点紧张,我们只挑重要的部分修改。把 ldap
部分的配置修改为实际的 ldap
参数,把 http->session_options->domain
修改为实际的服务器域名。然后执行 ./control start
运行
给 httpd
加一段反向代理,发布 oauth
服务
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto https
RemoteIPHeader X-Forwarded-For
ProxyPass "/resource/" "http://localhost:18080/resource/"
ProxyPass "/oauth/" "http://localhost:18080/oauth/"
ProxyPass "/user/" "http://localhost:18080/user/"
复制代码
ouath-server-lite
将 client
信息储存在数据库中。我们刚才使用了默认的 sqlite
模式,因此在首次启动后,他会自动创建 sqlite.db
文件并初始化表结构。现在我们调用管理接口生成 client
。如下所示,我们创建了一个授权域名是 developers.google.com
的 client
。然后就用它去 oauth playground 测试吧。
$ curl -H "X-API-KEY: shanghai-edu" -H "Content-Type: application/json" -d "{\"grant_type\":\"authorization_code\",\"domain\":\"developers.google.com\"}" http://127.0.0.1:18080/manage/v1/client
{"client_id":"8455e95a63d682bb","client_secret":"385a8174dd0799e220a8407f8ca6e8a9","grant_type":"authorization_code","domain":"developers.google.com","white_ip":"","scope":"Basic","description":""}
复制代码
endpoints
Authorization endpoint : /oauth/v1/authorize
Token endpoint : /oauth/v1/token
Userinfo endpoint : /oauth/v1/userinfo
对接 Shibboleth-IdP 3.4.6
其实 3.4.6 之后,由于新版插件均采用标准的 Externel
模式运行,因此 IdP 的 OAuth2 对接和 CAS 对接基本是完全一致的 —— 实际上 OAuth2 插件本来就是在 CAS 插件的基础上改的。有变化的地方仅 2 处。
引入的 jar
包替换为 shib-cas-authenticator-3.3.0-oauth.jar
idp.properties
中调整为 OAuth2 相关的配置。唯一需要注意的是,shibcas.oauth2principalname
应该配置为用户资源属性接口中,代表用户名的字段。例如我们使用 Apereo CAS
做 OAuth2
时,这里应该设置为 shibcas.oauth2principalname = id
,而使用 oauth-server-lite
时,则可以设置为 shibcas.oauth2principalname = sub
。当然,这里因为用的是同一个 ldap
,因此设置为 shibcas.oauth2principalname = uid
显然也都是可以的。总之这个字段存在就行。
更多细节可以关注 上海教育认证中心-IdP-Oauth2 对接 中的配置说明。
参考文献
oauth playground
CAS: OAuth-OpenId-Authentication
shanghai-edu/shib-cas-authn3/tree/3.3.0-oauth
shanghai-edu/oauth-server-lite
上海教育认证中心-IdP-Oauth2 对接
以上
原文于 2020 年 3 月首发于简书,搬家存档。
行文有微调。
评论