按照如下过程部署 proxy:
- 修改 conf 目录下的 config.yaml, 目前不支持修改该文件名
- 运行
luajit auto_config.lua
生成 nginx 配置片段
注意:若对 location 的匹配有顺序要求,需要开发者自己调整生成的 nginx 配置的 location 顺序.
我们结合如下的 config.yaml 示例来解释每一项配置的作用.
issuer: "https://sso.lain.com"
upstream: "http://127.0.0.1:8002"
oauth2_client:
redirect_uri: "http://example.com/authorize"
id: "23"
secret: "wpux1dtOdBzN6flwQpaFCQ"
csrf_cookie_name: "csrfunknown"
location:
- auth_type: "password"
match: "= /money"
- auth_type: "sms"
match: "~ \.(gif|jpg|jpeg)$"
- auth_type: "password sms"
match: "~ /score"
- match: "~ /api" # by default, auth_type is "password"
- auth_type: "none"
match: "~ /hello"
- issuer: 必选。另外,我们通过这个配置推断出 sso 的其他配置,如 (https://example.com/.well-known/openid-configuration) 所输出的配置。但我们在 lua 中并没有访问该 api, 当 sso 的相关配置改变时,请同时修改改 nginx proxy 相关的 lua 代码。
- upstream: 必选。由于哪些页面需要 sso 认证是可以配置的,不需要认证的页面直接会被代理至 upstream.
- oauth2_client: 该对象是指 oauth2 中所定义的 client, 需要 app maintainer 提前在对应 sso 中注册。csrf_cookie_name 是 app 定义的一个 cookie 名字,用来预防 csrf 攻击。需要说明的是,该对象下的4个属性中,id 和 secret 是必选的,当不设置 redirect_uri 时,相当于
redirect_uri = ${scheme}://${http_host}/_sso/
;csrf_cookie_name 的默认值为 sso_csrf. - location: 对应于 nginx 配置的 location. 该对象下是一个列表,每个列表规定了 2 种配置定义,其中,只有 match 是必写的.
- auth_type: 可能的选项为 "sms" "password" 等,也可以是它们的任意组合,该机制的进一步完善需要 sso 支持。默认情况下为 "password". 另外,如果 auth_type 为 "none",代表该 location 不需要登录,自然此时 group 不能为 true.
- match:
[ = | ~ | ~* | ^~ ] /uri
, 注意 nginx 的 location 匹配规则有四种情况,具体含义请参考 nginx 文档。
issuer: "https://sso.com"
upstream: "http://127.0.0.1:8002"
oauth2_client:
id: "24"
secret: "rvI9rcGyoRHknPsf-fCc_w"
csrf_cookie_name: "csrf"
location:
- auth_type: "none"
match: "~ /hello"
- match: "~ /"
对应生成的 auto.conf
set $myuser nginx;
set $user_groups nginx;
set $sso_upstream localhost;
location ~ /hello{
proxy_pass http://127.0.0.1:8002;
}
location ~ /{
rewrite_by_lua_file ./conf/rewrite.lua;
proxy_pass $sso_upstream;
proxy_set_header REMOTE-USER $myuser;
proxy_set_header USER-GROUPS $user_groups;
}
location ~ /_sso/{
# 虽然这里不生效,但事实上由于上一条经过了 rewrite.lua, 没有问题
rewrite_by_lua_file ./conf/rewrite.lua;
}
location /{
proxy_pass http://127.0.0.1:8002;
}
issuer: "https://sso.com"
upstream: "http://127.0.0.1:8002"
oauth2_client:
id: "24"
secret: "rvI9rcGyoRHknPsf-fCc_w"
csrf_cookie_name: "csrf"
location:
- match: "~ /finance"
生成如下的 conf 文件
set $myuser nginx;
set $user_groups nginx;
set $sso_upstream localhost;
location ~ /finance{
rewrite_by_lua_file ./conf/rewrite.lua;
proxy_pass $sso_upstream;
proxy_set_header REMOTE-USER $myuser;
proxy_set_header USER-GROUPS $user_groups;
}
location ~ /_sso/{
rewrite_by_lua_file ./conf/rewrite.lua;
}
location /{
proxy_pass http://127.0.0.1:8002;
}
为了尽可能少的需要应用改动代码,以快速对遗留应用进行 SSO 登录和授权,设计了在应用前端使用一层代理来处理 sso 相关处理的架构。
sequenceDiagram
User Agent->> Proxy: GET /resource
Note right of Proxy: Check cookie
Proxy-->> User Agent: Redirect
User Agent->> SSO: GET /auth?response_type=code&...
User Agent->> SSO: POST login & passwd
Note right of SSO: Validate password
SSO-->>User Agent: Redirect
User Agent->> Proxy: GET /_sso/code?code=...
Proxy->> SSO: POST /token code=...&client_secret=...
SSO-->> Proxy: Access Token & ID Token
Proxy-->> User Agent: Set cookie and redirect
User Agent->> Proxy: GET /resource with cookie
Note right of Proxy: Check cookie
Proxy->> App: GET /resource with REMOTE_USER header
App-->> Proxy: resource
Proxy-->> User Agent: resource
nginx 需要编译入 lua_nginx_module 以获得使用 lua 编制脚本的能力。
使用 lua 编写 proxy 的功能。包括:
-
对所有 url 非
oauth2_client.redirect_uri
的请求,检查 cookiesso
- 是否存在
- 如果存在,cookie 应当为一个 jwt (json web token), 语义上为一个 id_token
- 首先检查 jwt 是否有效,该步主要检查了 sso 的签名是否正确,是否超时
- 其次检查 id_token 满足设置的认证方式,即检查其内容,是否包含要求的 scope.
验证 sso 的组权限(组权限在应用内部管理?)
-
如果以上检查任意一项未通过,则返回 302 重定向:
HTTP/1.1 302 Found Location: ${config.sso_auth_endpoint} ?response_type=code &redirect_uri=${oauth2_client.redirect_uri} &realm=${config.realm} &client_id=${oauth2_client.id} &scope=openid ${auth_type} &state=${csrf}:${urlquote(ngx.var.request_uri)} Set-Cookie: ${oauth2_client.csrf_cookie_name}=${csrf}; Path=/; HttpOnly;
其中
csrf
为 request 中 cookie${oauth2_client.csrf_cookie_name}
的值,如果无此 cookie ,则随机生成一个值,base64url 格式。csrf 用来防御 Cross-Site Request Forgery 攻击。 -
如果访问的 url 是
oauth2_client.redirect_uri
,则检查state
参数的值的第一部分与${oauth2_client.csrf_cookie_name}
cookie 中的值是否一致-
如果不一致,或者无
sso_csrf
cookie, 返回 403 Forbidden. -
如果一致,使用 code 向 SSO 交换 ID token:
POST ${config.sso_token_endpoint} Content-Type: application/x-www-form-urlencoded code=${code}& client_id=${oauth2_client.id}& client_secret=${oauth2_client.secret}& redirect_uri=${oauth2_client.redirect_uri}& grant_type=authorization_code
-
如果成功换得 ID token,即收到如下 JSON:
{ "access_token": "...", "id_token": "...", "expires_in": ..., "token_type": "Bearer", "refresh_token": "..." }
- 设置 cookie_sso 为 ID token
Set-Cookie: sso=${id_token}; Path=/; HttpOnly;
- 重定向至 state 参数的第二部分
-
-
GET /resource with cookie
- 按照第一步的方法检查 cookie
sso
- 若通过检查,则解析 id_token 的 user_info 的 email 值作为 remote-user header
若 config.yaml 中,该页面对应 groups 为 true, 则调用 sso 的 api?, 用 id_token 换 groups 信息- GET /resource with REMOTE_USER header and User_Groups header
- App 信任该 header,检查用户,进行授权
- 按照第一步的方法检查 cookie
该功能还未开发完善,也没有在 Lain 上部署.