上篇文章中,我们提到了 Traefik 的 Forward Auth,本篇内容我们来展开聊聊如何使用它。

准备基础的 Web 服务Demo

这篇文章里,我们继续使用 whoami 作为 Web 服务,基础的配置文件和上一篇文章中一致,暂时不需要额外的设置:

version: '3'

services:

  whoami:
    image: containous/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"

      - "traefik.http.routers.test-auth-web.middlewares=https-redirect@file"
      - "traefik.http.routers.test-auth-web.entrypoints=http"
      - "traefik.http.routers.test-auth-web.rule=Host(`whoami.lab.com`, `whoami.lab.io`)"

      - "traefik.http.routers.test-auth-ssl.entrypoints=https"
      - "traefik.http.routers.test-auth-ssl.tls=true"
      - "traefik.http.routers.test-auth-ssl.rule=Host(`whoami.lab.com`, `whoami.lab.io`)"

    networks:
      - traefik

networks:
  traefik:
    external: true

使用容器配置 Traefik Forward Auth 服务

thomseddon/traefik-forward-auth 这个开源项目让我们在使用 Traefik 的时候,结合 Forward Auth 中间件,可以快速实现通用 OAuth / SSO 功能:

  • 支持多种验证“服务商”:Google/ 通用OAuth / 通用OIDC
  • 支持自定义请求服务器和指定路径,方便与现有系统集成
  • 支持基础的用户限制、授权来源限制、支持设置跨域 Cookie

简单来说,只要你的系统对外暴露服务是通过 Traefik,那么可以非常轻松愉快的使用这个模式为应用添加一层通用的前置 SSO 。可以达到效果类似我们在外部公网访问公司内网服务时候,会出现的一个登陆框,只有登陆成功后,才会展示我们想要看的内容。

使用这个方案的好处是,我们只需要结合一些简单的胶水代码,就可以做到背后的应用无修改接入或者几乎无修改接入,即使应用本身不支持 OAuth / SSO 方式接入,或者说我们无法直接修改的商业付费软件。

version: '3'

services:
  
  traefik-forward-auth:
    image: thomseddon/traefik-forward-auth:v2.2.0
    restart: always
    hostname: traefik-auth.lab.io
    environment:
      - LOG_LEVEL=trace
      - DEFAULT_PROVIDER=generic-oauth
      - PROVIDERS_GENERIC_OAUTH_AUTH_URL=https://sso.lab.io/dialog/authorize
      - PROVIDERS_GENERIC_OAUTH_TOKEN_URL=http://sso-web/oauth/token
      - PROVIDERS_GENERIC_OAUTH_USER_URL=http://sso-web/api/userinfo
      - PROVIDERS_GENERIC_OAUTH_USER_URL=http://sso-web/api/traefik-auth-user
      - PROVIDERS_GENERIC_OAUTH_CLIENT_ID=abc123
      - PROVIDERS_GENERIC_OAUTH_CLIENT_SECRET=ssh-secret
      - PROVIDERS_GENERIC_OAUTH_SCOPE=*
      - PROVIDERS_GENERIC_OAUTH_TOKEN_STYLE=header
      - SECRET=something-random
      - INSECURE_COOKIE=true
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"

      - "traefik.http.routers.traefik-auth-web.entrypoints=http"
      - "traefik.http.routers.traefik-auth-web.rule=Host(`traefik-auth.lab.com`, `traefik-auth.lab.io`)"

      - "traefik.http.routers.traefik-auth-ssl.entrypoints=https"
      - "traefik.http.routers.traefik-auth-ssl.rule=Host(`traefik-auth.lab.com`, `traefik-auth.lab.io`)"
      - "traefik.http.routers.traefik-auth-ssl.tls=true"

      - "traefik.http.middlewares.traefik-forward-auth.forwardauth.address=http://traefik-forward-auth:4181"
      - "traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
      - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181"
    networks:
      - traefik

networks:
  traefik:
    external: true

使用这个项目因为配置项比较多,而显得比较复杂,实际上并非如此,我们一点一点来理解它。

配置应用参数

我们在环境变量中定义了许多内容,这些内容的解释可以参考官方文档,这里我选择了 OAuth 作为授权服务配置,为了演示方便,我将他们运行在相同主机的相同容器网卡中,PROVIDERS_GENERIC_OAUTH_AUTH_URL 是用于用户在浏览器前端访问的地址,用于“确认授权”行为,所以需要配置对外访问的网络域名,除此之外,PROVIDERS_GENERIC_OAUTH_TOKEN_URLPROVIDERS_GENERIC_OAUTH_USER_URLPROVIDERS_GENERIC_OAUTH_USER_URL 可以都走容器内部通信,更加高效。

如果我们的 SSO 服务可以进行独立部署,那么这里四个 URL 变量需要都配置成可访问的域名地址,如果使用 https 协议,涉及自签名证书,需要重新构建容器。下一篇内容,我们再仔细展开 SSO 服务,这里稍作了解,有个印象就行啦。

environment:
  - LOG_LEVEL=trace
  - DEFAULT_PROVIDER=generic-oauth
  - PROVIDERS_GENERIC_OAUTH_AUTH_URL=https://sso.lab.io/dialog/authorize
  - PROVIDERS_GENERIC_OAUTH_TOKEN_URL=http://sso-web/oauth/token
  - PROVIDERS_GENERIC_OAUTH_USER_URL=http://sso-web/api/userinfo
  - PROVIDERS_GENERIC_OAUTH_USER_URL=http://sso-web/api/traefik-auth-user
  - PROVIDERS_GENERIC_OAUTH_CLIENT_ID=abc123
  - PROVIDERS_GENERIC_OAUTH_CLIENT_SECRET=secret
  - PROVIDERS_GENERIC_OAUTH_SCOPE=*
  - PROVIDERS_GENERIC_OAUTH_TOKEN_STYLE=header
  - SECRET=something-random
  - INSECURE_COOKIE=true

接着我们来进行服务路由的配置。

配置应用服务路由

配置服务路由比较简单,可以根据需求和喜好,设置是否“执行 HTTP 自动转发 HTTPS”等逻辑,设置方法上一篇文章中有描述,就不再赘述:

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=traefik"

  - "traefik.http.routers.traefik-auth-web.entrypoints=http"
  - "traefik.http.routers.traefik-auth-web.rule=Host(`traefik-auth.lab.com`, `traefik-auth.lab.io`)"

  - "traefik.http.routers.traefik-auth-ssl.entrypoints=https"
  - "traefik.http.routers.traefik-auth-ssl.tls=true"
  - "traefik.http.routers.traefik-auth-ssl.rule=Host(`traefik-auth.lab.com`, `traefik-auth.lab.io`)"
...

接着是配置 forwardauth 中间件,这里和配置应用参数情况类似,因为是同机部署演示,这里使用应用名称即可。如果是独立部署,需要替换为访问域名:

labels:
  ...
  - "traefik.http.middlewares.traefik-forward-auth.forwardauth.address=http://traefik-forward-auth:4181"
  - "traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders=X-Forwarded-User"
  - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181"

上面的中间件配置中还存在一个 authResponseHeaders 配置项 :用来向后续服务传递通过鉴权的用户信息,可以根据自己的需求进行修改或者移除。

最后,使用 docker-compose -d 启动应用,等待应用启动完毕,就可以准备应用接入啦。

完成应用配置

我们将文章开头的 Web 服务 Demo 配置中添加一条简单的配置规则,让刚刚配置的 traefik-forward-auth 加入到应用服务路由中:

version: '3'

services:

  whoami:
    image: containous/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
...
      - "traefik.http.routers.test-auth-ssl.middlewares=traefik-forward-auth@docker"

...
    networks:
      - traefik

networks:
  traefik:
    external: true

将内容单独保存一个新的 docker-compose.yml ,再次继续使用 docker-compose up -d 启动服务,接着进行服务效果验证。

验证 Forward Auth SSO 效果

打开浏览器,输入 whoami.lab.io ,可以看到首先是被重定向到了 https 协议,然后再次被重定向到了 sso.lab.io/... 的 SSO 鉴权地址,提示我们输入账号密码。

请求被重定向到了账号鉴权的地址

我们使用 curl 再来模拟一次服务端请求:

curl  https://whoami.lab.io -v    
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to whoami.lab.io (127.0.0.1) port 443 (#0)
...
> GET / HTTP/2
> Host: whoami.lab.io
> User-Agent: curl/7.64.1
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 307 
< content-type: text/html; charset=utf-8
< date: Wed, 02 Dec 2020 13:45:35 GMT
< location: https://sso.lab.io/dialog/authorize?client_id=abc123&redirect_uri=https%3A%2F%2Fwhoami.lab.io%2F_oauth&response_type=code&scope=%2A&state=396bd5c20d6bcfdffc2426bddf619707%3Ageneric-oauth%3Ahttps%3A%2F%2Fwhoami.lab.io%2F
< set-cookie: _forward_auth_csrf=396bd5c20d6bcfdffc2426bddf619707; Path=/; Domain=whoami.lab.io; Expires=Thu, 03 Dec 2020 01:45:35 GMT; HttpOnly
< content-length: 271
< 
<a href="https://sso.lab.io/dialog/authorize?client_id=abc123&amp;redirect_uri=https%3A%2F%2Fwhoami.lab.io%2F_oauth&amp;response_type=code&amp;scope=%2A&amp;state=396bd5c20d6bcfdffc2426bddf619707%3Ageneric-oauth%3Ahttps%3A%2F%2Fwhoami.lab.io%2F">Temporary Redirect</a>.

* Connection #0 to host whoami.lab.io left intact
* Closing connection 0

可以看到配置依然是生效的,服务端返回了 307 重定向,我们发送到 whoami.lab.io 的请求被转向到了 sso.lab.io ,符合我们的预期。

接着在浏览器中输入账号密码,点击提交,可以看到被重定向到了页面授权确认页面。

提示需要用户确认授权

点击允许,进行授权,等待授权完毕,我们就可以正式访问到应用的页面了。当然,也有一些应用会精简掉用户确认的对话框,让验证的整个流程更加的顺滑:

授权完毕,正常访问背后的应用

可以看到,应用请求头 X-Forwarded-User 和 Cookie 中可以看到通过授权的用户信息,可以进行进一步处理,或者鉴权规则的完善。

最后

写到这里,Traefik 基础鉴权验证的内容就完毕了,但是 SSO / OAuth 相关的内容才刚刚开始。

–EOF