これは、なにをしたくて書いたもの?
- Keycloak Gatekeeperというものが、Keycloak 4.6.0.Finalで追加されたらしく
- こちらを試してみようと
というわけで、Keycloak Gatekeeperを試してみようというエントリです。
Keycloak Gatekeeper?
Keycloak 4.6.0のリリースノートを見てみます。
Keycloak Gatekeeper provides a security proxy that can be used to secure applications and services without an adapter. It can be installed locally alongside your application or as a sidecar on OpenShift or Kubernetes.
アプリケーションの前にプロキシとして配置され、アプリケーションを保護する役割として配置されるようです。
OpenShiftやKubernetesとも一緒に使えるようですね。
ドキュメントは、こちら。
Keycloak Gatekeeper自身は、Golangで実装されているようですね。
GitHub - keycloak/keycloak-gatekeeper: A OpenID / Keycloak Proxy service
お題
Keycloak Gatekeeperの背後にサンプルアプリケーションを配置して、OpenID Connectによる認証で保護してみます。
以下の内容を実現してみます。
- ユーザーを2つ用意し、それぞれ異なるロール(user、admin)を割り当てる
- サンプルアプリケーションには、以下のようなエンドポイントを用意する
- ログインなしでもアクセスできる
- ログインしていればアクセスできる
- ログインしており、かつ特定のロールがないとアクセスできない
今回利用するKeycloakおよびKeycloak Gatekeeperのバージョンは、4.8.3.Finalとし、以下のような構成とします。
- サンプルアプリケーション … 192.168.0.2:5000
- Keycloak Gatekeeper … 172.17.0.2:3000
- Keycloak … 172.17.0.3:8080
サンプルアプリケーション
それでは、最初にサンプルアプリケーションを作成してみます。
環境確認と、アプリケーション作成に利用するExpressをインストール。
$ node -v v10.15.1 $ npm -v 6.4.1 $ npm i express
Expressのバージョンは、こちらです。
"dependencies": { "express": "^4.16.4" }
アプリケーションコード。
server.js
const express = require("express"); const app = express(); app.set("json spaces", 2); const handleRequest = (message, req, res) => { console.log(`[${new Date()}] access = ${req.path}`); const headers = req.headers; const response = { path: req.path, message, headers }; res.send(response); } // non-login app.get("/public/index", (req, res) => handleRequest("Hello Public Area!!", req, res)); // secure app.get("/secure/all", (req, res) => handleRequest("Hello Secure Area!!", req, res)); app.get("/secure/user", (req, res) => handleRequest("Hello Secure User Area!!", req, res)); app.get("/secure/admin", (req, res) => handleRequest("Hello Secure Admin Area!!", req, res)); console.log(`[${new Date()}] server startup`); app.listen(5000);
アプリケーション上は、特にログイン有無やロールの確認をしていません。
また、受け取ったHTTPヘッダーをレスポンスに含めるようにしています。
あとは、package.jsonで起動できるように設定して
"scripts": { "run": "node server.js", "test": "echo \"Error: no test specified\" && exit 1" },
起動。
$ npm start > ... > node server.js [Sat Feb 23 2019 18:48:53 GMT+0900 (GMT+09:00)] server startup
これで、準備は完了です。
Keycloak側の設定
Keycloakのダウンロードおよび展開、起動後、Keycloakの管理ユーザーを作成します。
$ bin/add-user-keycloak.sh -u keycloak-admin -p password $ bin/jboss-cli.sh -c reload
Keycloakの再起動が完了したら、作成したユーザーで管理コンソールからログインします。
以下のRealmを作成。
- Realm … demo-api
Clientsより、Clientを追加します。
- Client ID … sample-gatekeeper-api
- Client Protocol … openid-connect
- Root URL … http://172.17.0.2:3000
- Access Type … confidential
この時、「Mappers」を選び、Protocol Mapperを追加する必要があります。
- Mapper Type … Audience
- Included Client Audience … 作成したClientの名前(今回は「sample-gatekeeper-api」)
この手順が必要なのは、以下が理由です。
There is a known issue with the Keycloak server 4.7.0.Final in which Gatekeeper is unable to find the client_id in the aud claim. This is due to the fact the client_id is not in the audience anymore. The workaround is to add the "Audience" protocol mapper to the client with the audience pointed to the client_id.
作成したProtocol Mapperは、こちら。
では、続きを。
Rolesより、「user」と「admin」ロールを作成。
Usersからユーザーを2つ作成します。元からあるロールは、今回はそのままにしました。
- user001 / Role userを追加
- admin001 / Role adminを追加
Keycloak側の準備は、ここまでです。
Keycloak Gatekeeper
では、Keycloak Gatekeeper側を用意しましょう。
ダウンロードと展開。
$ wget https://downloads.jboss.org/keycloak/4.8.3.Final/gatekeeper/keycloak-gatekeeper-linux-amd64.tar.gz $ tar xf keycloak-gatekeeper-linux-amd64.tar.gz
展開される内容はいたってシンプルで、「keycloak-gatekeeper」というファイルひとつが出てきます。
Keycloak Gatekeeperは、起動引数でYAMLで書いた設定ファイルを指定するか、設定そのものを起動引数で与えるかで
設定を行います。
Example usage and configuration
今回は、YAMLファイルを用意しましょう。
gatekeeper.yml
client-id: sample-gatekeeper-api client-secret: fe557981-f9cf-4d46-8dff-b5ab37d3332a discovery-url: http://172.17.0.3:8080/auth/realms/demo-api listen: 0.0.0.0:3000 enable-refresh-tokens: true secure-cookie: false redirection-url: http://172.17.0.2:3000 encryption-key: 3lKvZzSmAq8DoMYGPRkVfSQrF27GKCPF upstream-url: http://192.168.0.2:5000 resources: - uri: /public/* white-listed: true - uri: /secure/all methods: - GET - uri: /secure/user methods: - GET roles: - user - uri: /secure/admin methods: - GET roles: - admin
設定内容は、以下の通りです。
- client-id、client-secret … 作成したClientのもの
- discovery-url … KeycloakのRealmに対するエンドポイント
- listen … Keycloak Gatekeeperのリッスンアドレスおよびポート
- enable-refresh-tokens … Refresh Tokenのハンドリングを有効にするか(デフォルトfalse)
- secure-cookie … バックエンドがhttpsではない場合、falseにする(デフォルトtrue)
- redirection-url … OAuthのコールバックURLを指定。Keycloak Gatekeeper(自分自身)に向ければOK
- encryption-key … セッションの暗号化キー。今回は、適当に作成
- upstream-url … バックエンドのURL
resources配下が、各リソースに対するアクセス設定です。今回は、以下のように設定しました。
- /public/* 配下 … 誰でもアクセス可(white-listed: true)
- /secure/all … ログインしていればアクセス可
- /secure/user … userロールを持っていればアクセス可
- /secure/admin … adminロールを持っていればアクセス可
Keycloak Gatekeeperでは、定義されていないリソースパスは全部要ログインな感じで弾かれるので、必要なパスは
きちんと定義しておきましょうね。
なお、設定項目は「--help」で確認できます。
$ ./keycloak-gatekeeper --help NAME: keycloak-gatekeeper - is a proxy using the keycloak service for auth and authorization USAGE: keycloak-gatekeeper [options] VERSION: 4.8.3 (git+sha: e5bd7e6-dirty, built: 15-01-2019) AUTHOR: Keycloak <keycloak-user@lists.jboss.org> COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --config value path the a configuration file [$PROXY_CONFIG_FILE] --listen value the interface the service should be listening on [$PROXY_LISTEN] --listen-http value interface we should be listening [$PROXY_LISTEN_HTTP] --discovery-url value discovery url to retrieve the openid configuration [$PROXY_DISCOVERY_URL] --client-id value client id used to authenticate to the oauth service [$PROXY_CLIENT_ID] --client-secret value client secret used to authenticate to the oauth service [$PROXY_CLIENT_SECRET] --redirection-url value redirection url for the oauth callback url, defaults to host header is absent [$PROXY_REDIRECTION_URL] --revocation-url value url for the revocation endpoint to revoke refresh token [$PROXY_REVOCATION_URL] --skip-openid-provider-tls-verify skip the verification of any TLS communication with the openid provider (default: false) --openid-provider-proxy value proxy for communication with the openid provider --openid-provider-timeout value timeout for openid configuration on .well-known/openid-configuration (default: 30s) --base-uri value common prefix for all URIs [$PROXY_BASE_URI] --oauth-uri value the uri for proxy oauth endpoints (default: "/oauth") [$PROXY_OAUTH_URI] --scopes value list of scopes requested when authenticating the user --upstream-url value url for the upstream endpoint you wish to proxy [$PROXY_UPSTREAM_URL] --upstream-ca value the path to a file container a CA certificate to validate the upstream tls endpoint --resources value list of resources 'uri=/admin*|methods=GET,PUT|roles=role1,role2' --headers value custom headers to the upstream request, key=value --preserve-host preserve the host header of the proxied request in the upstream request (default: false) --request-id-header value the http header name for request id (default: "X-Request-ID") [$PROXY_REQUEST_ID_HEADER] --response-headers value custom headers to added to the http response key=value --enable-self-signed-tls create self signed certificates for the proxy (default: false) [$PROXY_ENABLE_SELF_SIGNED_TLS] --self-signed-tls-hostnames value a list of hostnames to place on the self-signed certificate --self-signed-tls-expiration value the expiration of the certificate before rotation (default: 3h0m0s) --enable-request-id indicates we should add a request id if none found (default: false) [$PROXY_ENABLE_REQUEST_ID] --enable-logout-redirect indicates we should redirect to the identity provider for logging out (default: false) --enable-default-deny enables a default denial on all requests, you have to explicitly say what is permitted (recommended) (default: true) --enable-encrypted-token enable encryption for the access tokens (default: false) --enable-logging enable http logging of the requests (default: false) --enable-json-logging switch on json logging rather than text (default: false) --enable-forwarding enables the forwarding proxy mode, signing outbound request (default: false) --enable-security-filter enables the security filter handler (default: false) [$PROXY_ENABLE_SECURITY_FILTER] --enable-refresh-tokens enables the handling of the refresh tokens (default: false) [$PROXY_ENABLE_REFRESH_TOKEN] --enable-session-cookies access and refresh tokens are session only i.e. removed browser close (default: true) --enable-login-handler enables the handling of the refresh tokens (default: false) [$PROXY_ENABLE_LOGIN_HANDLER] --enable-token-header enables the token authentication header X-Auth-Token to upstream (default: true) --enable-authorization-header adds the authorization header to the proxy request (default: true) [$PROXY_ENABLE_AUTHORIZATION_HEADER] --enable-authorization-cookies adds the authorization cookies to the uptream proxy request (default: true) [$PROXY_ENABLE_AUTHORIZATION_COOKIES] --enable-https-redirection enable the http to https redirection on the http service (default: false) --enable-profiling switching on the golang profiling via pprof on /debug/pprof, /debug/pprof/heap etc (default: false) --enable-metrics enable the prometheus metrics collector on /oauth/metrics (default: false) --filter-browser-xss enable the adds the X-XSS-Protection header with mode=block (default: false) --filter-content-nosniff adds the X-Content-Type-Options header with the value nosniff (default: false) --filter-frame-deny enable to the frame deny header (default: false) --content-security-policy value specify the content security policy --localhost-metrics enforces the metrics page can only been requested from 127.0.0.1 (default: false) --access-token-duration value fallback cookie duration for the access token when using refresh tokens (default: 720h0m0s) --cookie-domain value domain the access cookie is available to, defaults host header --cookie-access-name value name of the cookie use to hold the access token (default: "kc-access") --cookie-refresh-name value name of the cookie used to hold the encrypted refresh token (default: "kc-state") --secure-cookie enforces the cookie to be secure (default: true) --http-only-cookie enforces the cookie is in http only mode (default: true) --match-claims value keypair values for matching access token claims e.g. aud=myapp, iss=http://example.* --add-claims value extra claims from the token and inject into headers, e.g given_name -> X-Auth-Given-Name --tls-cert value path to ths TLS certificate --tls-private-key value path to the private key for TLS --tls-ca-certificate value path to the ca certificate used for signing requests --tls-ca-key value path the ca private key, used by the forward signing proxy --tls-client-certificate value path to the client certificate for outbound connections in reverse and forwarding proxy modes --skip-upstream-tls-verify skip the verification of any upstream TLS (default: true) --cors-origins value origins to add to the CORE origins control (Access-Control-Allow-Origin) --cors-methods value methods permitted in the access control (Access-Control-Allow-Methods) --cors-headers value set of headers to add to the CORS access control (Access-Control-Allow-Headers) --cors-exposed-headers value expose cors headers access control (Access-Control-Expose-Headers) --cors-credentials credentials access control header (Access-Control-Allow-Credentials) (default: false) --cors-max-age value max age applied to cors headers (Access-Control-Max-Age) (default: 0s) --hostnames value list of hostnames the service will respond to --store-url value url for the storage subsystem, e.g redis://127.0.0.1:6379, file:///etc/tokens.file --encryption-key value encryption key used to encryption the session state [$PROXY_ENCRYPTION_KEY] --invalid-auth-redirects-with-303 use HTTP 303 redirects instead of 307 for invalid auth tokens (default: false) --no-redirects do not have back redirects when no authentication is present, 401 them (default: false) --skip-token-verification TESTING ONLY; bypass token verification, only expiration and roles enforced (default: false) --upstream-keepalives enables or disables the keepalive connections for upstream endpoint (default: true) --upstream-timeout value maximum amount of time a dial will wait for a connect to complete (default: 10s) --upstream-keepalive-timeout value specifies the keep-alive period for an active network connection (default: 10s) --upstream-tls-handshake-timeout value the timeout placed on the tls handshake for upstream (default: 10s) --upstream-response-header-timeout value the timeout placed on the response header for upstream (default: 10s) --upstream-expect-continue-timeout value the timeout placed on the expect continue for upstream (default: 10s) --verbose switch on debug / verbose logging (default: false) --enabled-proxy-protocol enable proxy protocol (default: false) --max-idle-connections value max idle upstream / keycloak connections to keep alive, ready for reuse (default: 0) --max-idle-connections-per-host value limits the number of idle connections maintained per host (default: 0) --server-read-timeout value the server read timeout on the http server (default: 10s) --server-write-timeout value the server write timeout on the http server (default: 10s) --server-idle-timeout value the server idle timeout on the http server (default: 2m0s) --use-letsencrypt use letsencrypt for certificates (default: false) --letsencrypt-cache-dir value path where cached letsencrypt certificates are stored (default: "./cache/") --sign-in-page value path to custom template displayed for signin --forbidden-page value path to custom template used for access forbidden --tags value keypairs passed to the templates at render,e.g title=Page --forwarding-username value username to use when logging into the openid provider [$PROXY_FORWARDING_USERNAME] --forwarding-password value password to use when logging into the openid provider [$PROXY_FORWARDING_PASSWORD] --forwarding-domains value list of domains which should be signed; everything else is relayed unsigned --disable-all-logging disables all logging to stdout and stderr (default: false) --help, -h show help --version, -v print the version
あとは、ドキュメントの記載を参考に。
Example usage and configuration
起動。
$ ./keycloak-gatekeeper --config gatekeeper.yml
ここまでで、Keycloak Gatekeeperの準備は完了です。
確認
では、確認してみましょう。
認証が不要な「http://172.17.0.2:3000/public/index」にアクセスしてみます。
アクセスできました。
では、認証が必要な「http://172.17.0.2:3000/secure/all」にアクセスしてみます。Keycloakのログイン画面が表示されるので、
今回は「user001」でログイン。
ログインに成功すると、リダイレクトされ「http://172.17.0.2:3000/secure/all」にアクセスできます。
テキストでは、こんな感じです。
{ "path": "/secure/all", "message": "Hello Secure Area!!", "headers": { "host": "192.168.0.2:5000", "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36", "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "accept-language": "ja,en-US;q=0.9,en;q=0.8", "authorization": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiaFRIWWZnRHUyRHhITjJJWXowVWpnQ1RjOG1lSGNyclV3NE5aT1Y5QW1VIn0.eyJqdGkiOiJjNmJkMWIyMy0xZTRkLTQyZGYtYTgwNS0zZWE1MTFkYmFkM2QiLCJleHAiOjE1NTA5MTgzMjgsIm5iZiI6MCwiaWF0IjoxNTUwOTE4MDI4LCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMzo4MDgwL2F1dGgvcmVhbG1zL2RlbW8tYXBpIiwiYXVkIjpbInNhbXBsZS1nYXRla2VlcGVyLWFwaSIsImFjY291bnQiXSwic3ViIjoiZDc3MzgyYjYtMTNiNC00NzMzLTlhZDEtM2YxNGNjMTkxZTNkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic2FtcGxlLWdhdGVrZWVwZXItYXBpIiwiYXV0aF90aW1lIjoxNTUwOTE4MDI4LCJzZXNzaW9uX3N0YXRlIjoiZTM4OTAyZDktY2M1ZC00YTQxLWIyMDctZWQ3MDBhYWY0NjM3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vMTcyLjE3LjAuMjozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIwMDEifQ.UjMLf21bUtfshItkqJ4JFkCLlgQNlN-nvfJgSaKGwHxl2YgUZgXGyvIcB37uDg9OSMgqrli7GFDnblDitHmWClV1KNRG3ohjuB86d3vNL-smBAOKAY6BhEiGN967LXSlvA1iMVZkyWmkoJqv_7KgmOuGOlduot4uF11neALCVIvFdwwsSbxJfWYQPdTFg4WB9rORa_1q11qrrWLwln3smazj-zBQR_29xWrFPHjlEg0Twpz28nT9DyR-qFieJvEQhHf0mLnEGCuscycaw2rQOjAwHFmr1BID_uk61W0LPLYzlt2XoOPaYARrPcAjwXKdQGquEDcicXrpUd4gxKepxA", "cache-control": "max-age=0", "cookie": "request_uri=L3NlY3VyZS9hbGw=; OAuth_Token_Request_State=1b5fab5f-4f23-4b9d-800c-c8a0ca2328f4; kc-access=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiaFRIWWZnRHUyRHhITjJJWXowVWpnQ1RjOG1lSGNyclV3NE5aT1Y5QW1VIn0.eyJqdGkiOiJjNmJkMWIyMy0xZTRkLTQyZGYtYTgwNS0zZWE1MTFkYmFkM2QiLCJleHAiOjE1NTA5MTgzMjgsIm5iZiI6MCwiaWF0IjoxNTUwOTE4MDI4LCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMzo4MDgwL2F1dGgvcmVhbG1zL2RlbW8tYXBpIiwiYXVkIjpbInNhbXBsZS1nYXRla2VlcGVyLWFwaSIsImFjY291bnQiXSwic3ViIjoiZDc3MzgyYjYtMTNiNC00NzMzLTlhZDEtM2YxNGNjMTkxZTNkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic2FtcGxlLWdhdGVrZWVwZXItYXBpIiwiYXV0aF90aW1lIjoxNTUwOTE4MDI4LCJzZXNzaW9uX3N0YXRlIjoiZTM4OTAyZDktY2M1ZC00YTQxLWIyMDctZWQ3MDBhYWY0NjM3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vMTcyLjE3LjAuMjozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIwMDEifQ.UjMLf21bUtfshItkqJ4JFkCLlgQNlN-nvfJgSaKGwHxl2YgUZgXGyvIcB37uDg9OSMgqrli7GFDnblDitHmWClV1KNRG3ohjuB86d3vNL-smBAOKAY6BhEiGN967LXSlvA1iMVZkyWmkoJqv_7KgmOuGOlduot4uF11neALCVIvFdwwsSbxJfWYQPdTFg4WB9rORa_1q11qrrWLwln3smazj-zBQR_29xWrFPHjlEg0Twpz28nT9DyR-qFieJvEQhHf0mLnEGCuscycaw2rQOjAwHFmr1BID_uk61W0LPLYzlt2XoOPaYARrPcAjwXKdQGquEDcicXrpUd4gxKepxA; kc-state=Caz2Dq85N44pnb6kbI5/w2K2MDlpZ0lh6QEHNThAnfzh3iEFr3K0QVGKtSCUf8auEoQMNQRt3/LTHUzhLv+juCpXX7NA3f0A7QpnxfLiTOl4AvQRGMmCrgxgC3spaA0RckOhJxTnSYC3iAjBdJwSWkKnrs7j3bs73uI7P8ubQJ3YP1MKFYMHsQHfFgcXy9S0UBu7ruFUjxl9AM1LGE+1dHmMXO8lXSZlG4a1AjC7cSi44stx3Ssn5qdB5lS+wlzGy807d4p/NLZmYKf1K9xvfOiRgLoI9HKYDg0V4cBa4fJDB4BhqiRCb7cBOmgyTmyIgxor+isCb4pmZyDsZln8pg0qTbh3voPj1OqGr1iAlNyZk3uZGbBQZQC+t2xLaypRIjSHSwxn+Jump0zFbJv1JLbHg57IK4WJhM6EDcIBSFFTeu1vxLLVFd5EWYh07Cq4288cXWcdD+RVZXmnLwIdgJ55Q5J+Z12rnLxPbc5jD6WIx1GL5ae4T4w+TG7BG029PYIRMzWoNs62qHy6Ai8v9jGa1H9iZy3JsAicDQ9oqYVYYWyN/WCN+gawAi460e7CuWd2PZuxzV/tSmPlGNi+wEmkIRQqzObmr9mLHWUFoI7UDHhy3KeQkgmphKgzRPaO0JVU52Psc9cFVbV4/GFFLpA+H9Fj47BRMa6ed5TqGLXYzKHmlMBtH16pJdLKIxjNqP09CaYH4/Ej7bPSbOCMeigapiZrhz1trkfbjin2oLBysCiRoqILBtMV11bnK315dzmdD+ycyyaWZMknCDAFxkoaoLPuXk/3sv/lRxhwsgp2rOk5PEJyV2lmJSLms+EDRzsnnLaHNrIMJOcfja2VmGUTsOIzLh9Fvr8odk53/elUYgBCl9OZRPsCMapDeL7WkUhtXXuz2jYKuDeA79+YEUvy4WsQLtzzWd3FKNaG1CwH1cC7vGcyO9zWt/+WsvtHdBvR+JMOwkq23sbj+tH5hhmKH9Kkeq5XohYAc352+JbMjYuxJ8yfhBxeRIlONxERI0Rx01MKGRxItgRi3uwOegJyi7PkVPHE0TqwcxVD88LU9ggS4rVZRGWT/gWl+YpvZLtaspIToLm9qzn8P90rJw3/j6JMFP02DpqnNth1wmct+xcTm5p64ZGi47uLS1lGUKXGotk7YfgAEzPYGmt3ZsLnoPE2ZCYq8eNB9UtbZRbZKbUM63RgSD2hnBAmZg", "referer": "http://172.17.0.3:8080/auth/realms/demo-api/protocol/openid-connect/auth?client_id=sample-gatekeeper-api&redirect_uri=http%3A%2F%2F172.17.0.2%3A3000%2Foauth%2Fcallback&response_type=code&scope=openid+email+profile&state=1b5fab5f-4f23-4b9d-800c-c8a0ca2328f4", "upgrade-insecure-requests": "1", "x-auth-audience": "sample-gatekeeper-api,account", "x-auth-email": "", "x-auth-expiresin": "2019-02-23 10:38:48 +0000 UTC", "x-auth-groups": "", "x-auth-roles": "offline_access,uma_authorization,user,account:manage-account,account:manage-account-links,account:view-profile", "x-auth-subject": "d77382b6-13b4-4733-9ad1-3f14cc191e3d", "x-auth-token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiaFRIWWZnRHUyRHhITjJJWXowVWpnQ1RjOG1lSGNyclV3NE5aT1Y5QW1VIn0.eyJqdGkiOiJjNmJkMWIyMy0xZTRkLTQyZGYtYTgwNS0zZWE1MTFkYmFkM2QiLCJleHAiOjE1NTA5MTgzMjgsIm5iZiI6MCwiaWF0IjoxNTUwOTE4MDI4LCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMzo4MDgwL2F1dGgvcmVhbG1zL2RlbW8tYXBpIiwiYXVkIjpbInNhbXBsZS1nYXRla2VlcGVyLWFwaSIsImFjY291bnQiXSwic3ViIjoiZDc3MzgyYjYtMTNiNC00NzMzLTlhZDEtM2YxNGNjMTkxZTNkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic2FtcGxlLWdhdGVrZWVwZXItYXBpIiwiYXV0aF90aW1lIjoxNTUwOTE4MDI4LCJzZXNzaW9uX3N0YXRlIjoiZTM4OTAyZDktY2M1ZC00YTQxLWIyMDctZWQ3MDBhYWY0NjM3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vMTcyLjE3LjAuMjozMDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIwMDEifQ.UjMLf21bUtfshItkqJ4JFkCLlgQNlN-nvfJgSaKGwHxl2YgUZgXGyvIcB37uDg9OSMgqrli7GFDnblDitHmWClV1KNRG3ohjuB86d3vNL-smBAOKAY6BhEiGN967LXSlvA1iMVZkyWmkoJqv_7KgmOuGOlduot4uF11neALCVIvFdwwsSbxJfWYQPdTFg4WB9rORa_1q11qrrWLwln3smazj-zBQR_29xWrFPHjlEg0Twpz28nT9DyR-qFieJvEQhHf0mLnEGCuscycaw2rQOjAwHFmr1BID_uk61W0LPLYzlt2XoOPaYARrPcAjwXKdQGquEDcicXrpUd4gxKepxA", "x-auth-userid": "user001", "x-auth-username": "user001", "x-forwarded-for": "172.17.0.1", "x-forwarded-host": "172.17.0.2:3000", "x-forwarded-proto": "", "accept-encoding": "gzip" } }
続いて、「http://172.17.0.2:3000/secure/user」へアクセス。表示される画面の内容は、先ほどと変わらないので割愛。
では、「http://172.17.0.2:3000/secure/admin」にアクセス。
こちらは、表示する権限がありません。
ここで1度Cookieを消し、Keycloakからもログアウトして、今度は「admin001」ユーザーでログインし直します。
※Keycloakからのログアウトには、Sessionsから強制ログアウトさせました
すると、今度は参照可能になります。
今回はadminロールを持つユーザーに、userロールを持たせていないので、ロールでアクセス制限を行っているリソースへは、
どちらか片方しかアクセスできませんが…。
トラブルシュート
ログインしたのに保護されたリソースにうまくアクセスできない場合は、Protocol Mapperの設定が疑われます。
以下のようなログが、Keycloak Gatekeeperに出力されていないか確認しましょう。
1.5501313610580595e+09 error unable to verify the id token {"error": "oidc: JWT claims invalid: invalid claim value: 'aud' is required, and should be either string or string array"}
「aud」(Audience:トークンの受け手)を表すクレームが必要だよ、と言われています。
先述したProtocol Mapperは、ここに効いてきます。
There is a known issue with the Keycloak server 4.7.0.Final in which Gatekeeper is unable to find the client_id in the aud claim. This is due to the fact the client_id is not in the audience anymore. The workaround is to add the "Audience" protocol mapper to the client with the audience pointed to the client_id.
制限
Kecloak Gatekeeperの制限。Cookieに関するものが書かれていますね。
まとめ
今回は、Keycloak Gatekeeperを試してみました。「aud」クレームで若干ハマり、Keycloak自体でハマったものの、なんとか
動かすことはできました。
動かしていて、まだまだOpenID Connectとかわかってないなぁということを強く思ったので、勉強しないと…。