6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Keycloakの認可機能の使い方(RBAC編)

Last updated at Posted at 2020-09-06

Keycloakの認可機能への理解を深めるため、実際に保護対象のリソースと認可ルール(ポリシー・パーミッション)を設定し、登録したクライアントとユーザーの組み合わせで取得したアクセストークンでの認可判定の動作を確認した。

今回はアクセス制御モデルとしてよく使われるロールベースアクセス制御(RBAC)を設定する。リソースの認可ルールとしてロールを条件(ロールベースポリシー)として登録し、ユーザーにロールを割り当てていない状態、割り当てた状態での認可判定を実施する。

なお、各種設定は管理コンソールを使わず、基本的にAdmin REST APIで行う。

検証環境

Docker(Docker Compose)で以下の構成の検証環境を立ち上げる。

  • Keycloak 11.02
  • Ubuntu 22.04(curlコマンドでAPIを呼び出すためのクライアントとして)

docker-compose.yml

version: '3'
services:
  curl_jq:
    tty: true
    build:
      context: .
  keycloak:
    image: quay.io/keycloak/keycloak:11.0.2
    ports:
      - 8080:8080
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin

※ 動作検証目的のため、Keycloakの管理者ユーザー名・パスワードを admin に設定している。

Dockerfile

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl jq

動作確認手順・結果

  • 管理者用アクセストークンを取得する関数を用意する。

    BASE_URI=http://keycloak:8080/auth
    get-token() {
      ACCESS_TOKEN=$(curl -s -X POST \
        "${BASE_URI}/realms/master/protocol/openid-connect/token" \
        -d "client_id=admin-cli" \
        -d "username=admin" \
        -d "password=admin" \
        -d "grant_type=password" \
        | jq -r '.access_token')
      echo ACCESS_TOKEN = ${ACCESS_TOKEN}
    }
    
  • ロール(role-1)を登録する。

    ROLE_NAME=role-1
    get-token
    ROLE_URI=$(curl -s -X POST \
      "${BASE_URI}/admin/realms/master/roles" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      -D - \
      --data-binary @- << EOF | grep -oP '(?<=Location: ).+(?=\r)'
    {
      "name": "${ROLE_NAME}"
    }
    EOF
    )
    echo ROLE_URI = ${ROLE_URI}
    ROLE_ID=$(curl -s ${ROLE_URI} \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      | jq -r .id)
    echo ROLE_ID = ${ROLE_ID}
    
  • リソースサーバー(resource-server-1)と保護対象となるリソース(resource-1)とrole-1を認可条件とするロールベースポリシー(policy-1)を登録する。
    ※ KeycloakではリソースサーバーはOpenID ConnectのConfidentialクライアントとして登録する。

    RESOURCE_SERVER_CLIENT_ID=resource-server-1
    RESOURCE_NAME=resource-1
    POLICY_NAME=policy-1
    get-token
    RESOURCE_SERVER_ID=$(curl -s -X POST \
      "${BASE_URI}/admin/realms/master/clients" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      -D - \
      --data-binary @- << EOF | grep -oP '(?<=clients/).+(?=\r)'
    {
      "clientId": "${RESOURCE_SERVER_CLIENT_ID}",
      "protocol": "openid-connect",
      "publicClient": false,
      "serviceAccountsEnabled": true,
      "authorizationServicesEnabled": true,
      "authorizationSettings": {
        "resources": [{ "name": "${RESOURCE_NAME}", "displayName": "Resource 1" }],
        "policies": [{
          "type": "role",
          "logic": "POSITIVE",
          "decisionStrategy": "UNANIMOUS",
          "name": "${POLICY_NAME}",
          "config": { "roles": "[{\"id\":\"${ROLE_ID}\"}]" }
        }]
      }
    }
    EOF
    )
    echo RESOURCE_SERVER_ID = ${RESOURCE_SERVER_ID}
    
  • 登録したリソース(resource-1)とポリシー(policy-1)を紐づけるパーミッション(permission-1)を設定する。

    get-token
    curl -s -X POST \
      "${BASE_URI}/admin/realms/master/clients/${RESOURCE_SERVER_ID}/authz/resource-server/permission/resource" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      --data-binary @- << EOF | jq .
    {
      "type": "resource",
      "logic": "POSITIVE",
      "decisionStrategy": "UNANIMOUS",
      "name": "permission-1",
      "resources": ["${RESOURCE_NAME}"],
      "policies": ["${POLICY_NAME}"]
    }
    EOF
    
  • リソースの利用者となるクライアント(client-1)を登録する。

    CLIENT_ID=client-1
    get-token
    curl -s -X POST \
      "${BASE_URI}/admin/realms/master/clients" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      --data-binary @- << EOF
    {
      "clientId": "${CLIENT_ID}",
      "protocol": "openid-connect",
      "publicClient": true
    }
    EOF
    
  • ユーザー(user-1)を登録してパスワードを設定する。

    USER_NAME=user-1
    USER_PASSWORD=password
    get-token
    USER_ID=$(curl -s -X POST \
      "${BASE_URI}/admin/realms/master/users" \
      -D - \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      --data-binary @- << EOF | grep -oP '(?<=users/).+(?=\r)'
    {
      "username": "${USER_NAME}",
      "enabled": true
    }
    EOF
    )
    echo USER_ID = ${USER_ID}
    get-token
    curl -s -X PUT \
      "${BASE_URI}/admin/realms/master/users/${USER_ID}/reset-password" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      --data-binary @- << EOF
    {
      "type": "password",
      "value": "${USER_PASSWORD}",
      "temporary": false
    }
    EOF
    
  • ユーザー(user-1)にロール(role-1)を割り当てない状態で認可判定を行うとアクセス拒否となる。
    ※ アクセストークン取得は簡易的にPassword Flowで行っている。

    CLIENT_ACCESS_TOKEN=$(curl -s -X POST \
        "${BASE_URI}/realms/master/protocol/openid-connect/token" \
        -d "grant_type=password" \
        -d "client_id=${CLIENT_ID}" \
        -d "username=${USER_NAME}" \
        -d "password=${USER_PASSWORD}" \
        | jq -r '.access_token'
    )
    echo CLIENT_ACCESS_TOKEN = ${CLIENT_ACCESS_TOKEN}
    curl -s -X POST \
      "${BASE_URI}/realms/master/protocol/openid-connect/token" \
      -H "Authorization: Bearer ${CLIENT_ACCESS_TOKEN}" \
      -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
      -d "response_mode=decision" \
      -d "audience=${RESOURCE_SERVER_CLIENT_ID}" \
      -d "permission=${RESOURCE_NAME}" \
      | jq .
    # => { "error": "access_denied", "error_description": "not_authorized" }
    
  • ユーザー(user-1)にロール(role-1)を割り当てる。

    get-token
    curl -s -X POST \
      "${BASE_URI}/admin/realms/master/users/${USER_ID}/role-mappings/realm" \
      -H "Authorization: Bearer ${ACCESS_TOKEN}" \
      -H "Content-Type: application/json" \
      --data-binary @- << EOF | jq .
    [{
      "id": "${ROLE_ID}",
      "name": "${ROLE_NAME}"
    }]
    EOF
    
  • 再度認可判定を行うとアクセス許可となる。

    CLIENT_ACCESS_TOKEN=$(curl -s -X POST \
        "${BASE_URI}/realms/master/protocol/openid-connect/token" \
        -d "grant_type=password" \
        -d "client_id=${CLIENT_ID}" \
        -d "username=${USER_NAME}" \
        -d "password=${USER_PASSWORD}" \
        | jq -r '.access_token'
    )
    echo CLIENT_ACCESS_TOKEN = ${CLIENT_ACCESS_TOKEN}
    curl -s -X POST \
      "${BASE_URI}/realms/master/protocol/openid-connect/token" \
      -H "Authorization: Bearer ${CLIENT_ACCESS_TOKEN}" \
      -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
      -d "response_mode=decision" \
      -d "audience=${RESOURCE_SERVER_CLIENT_ID}" \
      -d "permission=${RESOURCE_NAME}" \
      | jq .
    # => { "result": true }
    

参考

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?