はじめに
まず、OCI IAM と連携して Deep Data Security を適用する方法を解説した こちらの記事に目を通して頂きたく 🙇
概要が把握できたところで、本題に移りましょう。
この記事の目的
Webアプリケーションのような、複数のユーザーからのリクエストをリアルタイムに処理するアプリケーションでは、パフォーマンスやスケーラビリティの観点から Connection Pool を使うのが一般的です。もし使わなかったとしたら、毎回コストの高いデータベースへの接続処理が発生し、これがレスポンスの低下を招き、ユーザー・エクスペリエンスがガタ落ちになるでしょう。
では Oralce Universal Connection Pool (UCP) をはじめとする JDBC系の Connection Pool を使う場合、Deep Data Security と組み合わせるにはどうすればいいの? というのが今回のメインテーマです。Connection Pool を使う/使わないに関わらず 結局 java.sql.Connection オブジェクトを使ってデータベースの操作を行うわけですが、Java 標準 (JDBC) の Connection クラスの仕様に Oracle 独自の Deep Data Security に関する仕様など当然入っているはずもなく...。
前述の記事に、実は正解は書いてあって、EndUserSecurityContext オブジェクトを作成して oracle.jdbc.OracleConnection にセットすることで実現できます。
import java.sql.Connection;
import oracle.jdbc.EndUserSecurityContext;
import oracle.jdbc.OracleConnection;
// ...
// Connection Pool から Connection を取り出す
try (Connection connection = pool.getConnection()){
// connection は oracle.jdbc.OracleConnection の派生クラスとは限らない
OracleConnection oracleConnection =
connection instanceof OracleConnection
? (OracleConnection) connection
: connection.unwrap(OracleConnection.class);
// EndUserSecurityContext を作成する
EndUserSecurityContext eusc = EndUserSecurityContext
.createWithToken(DB_ACCESS_TOKEN, USER_TOKEN)
// EndUserSecurityContext を Connection にセット
oracleConnection.setEndUserSecurityContext(eusc);
try {
// SQLの発行
} finally {
// EndUserSecurityContext をクリア
oracleConnection.clearEndUserSecurityContext();
}
}
なるほど... って思うかも知れませんが、では DB_ACCESS_TOKEN や USER_TOKEN ってどうやって取得すればいいのでしょうか?
ということで、今回のゴールは
- EndUserSecurityContext の作成に必要な、
DB_ACCESS_TOKENとUSER_TOKENの取得方法を解説する
です。
Why MCP Server ?
Deep Data Security と UCP の組み合わせを検証するのに MCP Server である必要は全くないのですが、先日 Oracle Developer Day 2026 への登壇に際し、NL2MQL (Monitoring Query Language) のデモを作成したので、その延長線で自前の NL2SQL の基盤となる MCP Server を作ってみたかっただけです... 😊
とは言いつつも、NL2SQL のシナリオでは LLM が自由に SQL 文を作って実行する訳ですから、データへのアクセスを適切に管理することはとても重要です。
Connection Pool を前提にした場合、接続は共有するので、アクセス・コントロールはセッション・レベルで行う必要があります。従来であれば、データベースのセッション・ロールをユーザーの属性に合わせてアプリケーション側で切り替えるような実装を考える必要がありましたが、Deep Data Security であれば、エンド・ユーザーのコンテキストをデータベース側に渡すだけで OK なので、アプリケーションの実装がシンプルになり(設定ミス起因のセキュリティ事故も起きにくい)、データへのアクセス・コントロールもデータベース側で一元的に管理することが可能です。
今回は、シンプルに SQL文をリクエストとして受け取って、実行結果をレスポンスとして返す MCP Server Tool を実装します。ソースはこちら
MCP Server のフレームワークとして Helidon MCP Server を使います。
また MCP クライアント(ここでユーザーの OAuth 認証が発動します)として、MCP Inspector を使います。OAuth認証のところまで面倒を見てくれるので、非常に使い勝手の良い検証ツールです。
全体の構成はこのようになっています。
EndUserSecurityContext オブジェクトの作成
繰り返しになりますが
EndUserSecurityContext.createWithToken(DB_ACCESS_TOKEN, USER_TOKEN)
を実行するために必要な2つの引数の取得方法がポイントですが、最初に答えを書いておきます。
-
USER_TOKENは MCP Inspector が OAuth Authorization Code Grant 方式で取得した「ユーザー」のアクセス・トークンで、MCPリクエストの Authorization ヘッダーにセットされて MCP Server に渡ります -
DB_ACCESS_TOKENは MCP Server が OAuth Client Credentials Grant 方式で取得した「統合アプリケーション」のアクセス・トークンです
MCP Server の内部で Connection Pool が管理されている訳ですが、MCP Server は MCP Client から送られてきた USER_TOKEN と、自ら IAM から取得した DB_ACCESS_TOKEN を用いて EndUserSecurityContext を作成し、Connection Pool から所得した Connection にセットします。
OCI IAM Identity Domains の設定
冒頭で紹介した Qiita 記事にある通り、IAM ドメインに 統合アプリケーション (Integrated Application) が必要です。統合アプリケーションを2つ用意して、リソース・サーバー構成 と クライアント構成 を別々の統合アプリケーションに設定することも可能ですが、1つの統合アプリケーションに リソース・サーバー構成 と クライアント構成 の両方を設定して問題ありません。
- リソース・サーバー構成
- プライマリ・オーディエンスの設定
- スコープの追加
- クライアント構成
- 許可される権限付与タイプ (Grant Types)で以下をチェック
- リソース所有者 (Resource Owner)
- クライアント資格証明 (Client Credentials)
- JWTアサーション (JWT Assertion)
- リフレッシュ・トークン (Refresh Token) - オプション
- 認可コード (Authorization Code)
- リダイレクトURL:
http://localhost:6274/oauth/callback
- 許可される権限付与タイプ (Grant Types)で以下をチェック
リダイレクトURL は MCP Inspector と連動する際に必要な設定です。
Custom Claim 追加ルールの作成
OCI IAM では group という Claim は標準ではないので、アクセス・トークンに group という Custom Claim を追加するためのルールを設定しないといけません。これには、クライアントのアプリケーション・ロール に Identity Domain Administrator が無いと API を操作する権限が付与されませんので、以下のスクリプトを実行する間だけでも、クライアントにアプリケーション・ロールを追加して下さい(ドメイン共通なので、別の統合アプリケーションで操作しても構わないです)。
#!/bin/bash
# "group" Custom Claim を追加する
DOMAIN_URL="https://idcs-xxxx.identity.oraclecloud.com"
CLIENT_ID="xxxxxxxxx"
CLIENT_SECRET="idcscs-xxxxxxxx"
ACCESS_TOKEN=$(curl -s -X POST "${DOMAIN_URL}/oauth2/v1/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-d "grant_type=client_credentials&scope=urn:opc:idm:__myscopes__" \
| jq -r '.access_token')
curl -X POST "${DOMAIN_URL}/admin/v1/CustomClaims" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": [
"urn:ietf:params:scim:schemas:oracle:idcs:CustomClaim"
],
"name": "group",
"value": "$user.groups.*.display",
"expression": true,
"mode": "always",
"tokenType": "AT",
"allScopes": true
}'
USER_TOKEN の取得
USER_TOKEN は、IAM で OAuth(OIDC) 認証したユーザーの jwt アクセストークンです。アプリケーション・サーバーなどでベタに取得しようとするなら HTTP Request Header の Authorization: Bearer <token> の token を取り出せばいいです。
このあたりは、様々なフレームワークで jwt トークンを扱う方法がありますので、利用するフレームワークの流儀に従って jwt トークンを取得してもらえればいいです。
例えば Eclipse MicroProfile 仕様であれば、
@Inject private JsonWebToken callerPrincipal;
// JsonWebToken.getRawToken() で 生のjwtトークンを取り出せる
のように、インジェクションを使って、jwt トークンを取り出すことができます。
今回は、Hellidon MCP Server を使っていますが、この場合だと McpToolRequest から取り出すことができます。
import io.helidon.extensions.mcp.server.McpToolRequest;
import io.helidon.security.SecurityContext;
import io.helidon.security.providers.common.TokenCredential;
//...
private String getEndUserToken(McpToolRequest request) {
return endUserToken = request.requestContext()
.get(SecurityContext.class)
.orElseThrow(() -> new RuntimeException("SecurityContext not found"))
.user()
.flatMap(s -> s.publicCredential(TokenCredential.class))
.map(TokenCredential::token)
.orElse(null);
}
DB_ACCESS_TOKEN の取得
DB_ACCESS_TOKEN は、IAM Idnetity Domains で作成した 統合アプリケーション のアクセス・トークンです。
Shell で書くとこうなります。
#!/bin/bash
# Client Credentials Grant で統合アプリケーションのアクセス・トークンを取得する
DOMAIN_URL="https://idcs-xxxx.identity.oraclecloud.com"
CLIENT_ID="xxxxxxxxx"
CLIENT_SECRET="idcscs-xxxxxxxx"
AUDIENCE="xxxxxxxx"
SCOPE="xxxxxxxx"
curl -s -X POST "${DOMAIN_URL}/oauth2/v1/token" \
-H "Content-Type:application/x-www-form-urlencoded" \
-u "${CLIENT_ID}:${CLIENT_SECRET}" \
-d "grant_type=client_credentials" \
-d "scope=${AUDIENCE}${SCOPE}" \
| jq -r '.access_token'
リクエスト毎に異なる値である可能性のある USER_TOKEN と違って、DB_ACCESS_TOKEN は有効期限が来るまでキャッシュしておけるので、効率良い実装を考えて下さい(今回作った MCP Server はキャッシュの仕組みを実装しています)。
UCP で使う Database ユーザー
UCP で利用する Database ユーザーは CREATE SESSION と CREATE END USER SECURITY CONTEXT のシステム権限さえ持っていれば大丈夫です。セッションにセットされた EndUserSecurityContext に従って、Deep Data Security が動的にセッションに対して権限を付与します。
GRANT CREATE SESSION TO CONNECTION_POOL_USER;
GRANT CREATE END USER SECURITY CONTEXT TO CONNECTION_POOL_USER;
MCP Server を動かしてみる
IAM の準備
ユーザー sking (Steven King) と グループ employees を作成して、sking を employees グループに入れます。
ドメインの統合アプリケーションの設定は前述の通りで、ここにグループ employees を追加します。
Autonomous AI Database の準備
サンプル・スキーマ から HR スキーマをインストールします。
また、IAMとの連携のための設定や DATA ROLE, DATA GRANTの作成を行います。
BEGIN
DBMS_CLOUD_ADMIN.ENABLE_EXTERNAL_AUTHENTICATION(
type => 'OCI_IAM',
params => JSON_OBJECT(
'app_id' VALUE '{{ 統合アプリケーションの ID }}',
'domain_url' VALUE 'https://idcs-xxx.identity.oraclecloud.com:443'
),
force => TRUE
);
END;
/
BEGIN
DBMS_CLOUD.CREATE_CREDENTIAL(
credential_name => 'OCI_IAM_DOMAIN_DB_CRED$',
username => '{{ 統合アプリケーションの Client ID }}',
password => '{{ 統合アプリケーションの Client Secret }}'
);
END;
/
-- データ・ロールをIAMグループとマッピングして作成
CREATE OR REPLACE DATA ROLE hrapp_employees
MAPPED TO 'IAM_OAUTH_GROUP=EMPLOYEES';
-- 従業員は自分の行だけセレクトできる
CREATE OR REPLACE DATA GRANT hr.hrapp_employees_access
AS SELECT
ON hr.employees
WHERE upper(email) = upper(ORA_END_USER_CONTEXT.USERNAME)
TO hrapp_employees;
Helidon MCP Server の起動
# Java & Maven がインストール済の環境で
# ソースのダウンロード & パッケージ
$ git clone https://github.com/tkote/sql-mcp-server
$ cd sql-mcp-server/
$ mvn clean package
# run.sh を編集して環境変数をセットしてから
$ ./run.sh
MCP Inspector の起動
# Node.js がインストール済の環境で
$ npx @modelcontextprotocol/inspector
MCP Inspector が起動したら、以下の設定を行なって下さい。
- Transport Type: Streamable HTTP
- URL: http://localhost:8080/mcp
- Connection Type: Via Proxy
- OAuth 2.0 Flow
- Client ID:
Client ID - Client Secret:
Client Secret - Redirect URL: http://localhost:6274/oauth/callback
- Scope:
openid {{ audience/scope }}
- Client ID:
認証サーバーのエンドポイントの設定がありませんが、MCPプロトコルの Metadata Discovery という仕組みによって、MCP Server がエンドポイントを教えてくれます。
Connectを押すと、IAMの認証画面に遷移し、...
認証が成功すると以下のような画面になります。
認証で失敗しても容易に切り分けができるように、ステップ・バイ・ステップ で認証フローを確認することができます。
Deep Data Security の効果を確かめる
ORA_END_USER_CONTEXT を確認する
ユーザー sking でログインしている状態で、SELECT ORA_END_USER_CONTEXT.USERNAME FROM DUAL を実行します。
ORA_END_USER_CONTEXT がちゃんと取れているか確認します。
HR.EMPLOYEES テーブルを SELECT する
SELECT * FROM HR.EMPLOYEES を実行します。
ユーザー sking 本人の1行しか SELECT されていないことを確認して下さい。
おまけ: Claude Code から問い合わせる
ここまで LLM が一切出てこなかったので、MCP Insspector の代わりに Claude Code を使って、簡易版の NL2SQL をやってみます。
Claude Code のプロジェクト・ディレクトリに .mcp.json を作成して、MCP Server を登録します。Claude Code で扱うことのできる MCP Server は、認証が必要な場合に Dynamic Client Registration できることが前提なので、OAuth の Client ID/Secret を設定することができません。一方 OCI IAM も Dynamic Client Registration には対応していないので、Client ID/Secret を省略することはできません。
こういう場合は、Authorization ヘッダを直接書いてしまいます。アクセス・トークンは MCP Inspector で認証を行って、Auth タブのページから取得しましょう。
では、実行...
うまくいきました!
まとめ
適用シーンについて
Deep Data Security を使えば、エンド・ユーザーのコンテキストをデータベースのレイヤーまで伝播させて、データに対する一貫的なアクセス・コントロールを実現できます。
エンド・ユーザーがデータベースのスキーマを意識して直接操作するような業務に対して、セキュリティを確保するようなユースケースが適用シーンとして分かりやすいと思います -- データ・アナリストが行うデータ分析業務やシステム・オペレーターが行うデータベース管理業務など。
それと、AI の文脈でいけば エンド・ユーザーの代理で働く LLM が行うデータベースへの操作に対して、エンド・ユーザーのコンテキストでガードをかけるようなケース(まさに今回の記事!)も今後重要度が増してくるのでは無いでしょうか?
アプリケーション・レベルでのアクセス・コントロール
Web 3層アプリケーションや API サーバーのような場合だと、業務要件を考慮し設計された API レイヤーが手前にあって、ここから背後にある正規化されたデータ構造を持つデータベースに対して CRUD 処理を行ったり、クエリした結果をキャッシュしたりするのが一般的で、こういったケースでは、なかなかエンド・ユーザーのコンテキストの単位でデータベースのアクセス・コントロールを行うことは困難です。しかし、この場合でもアプリケーション(IAM の統合アプリケーション)単位で、Deep Data Security のアクセス・コントロールを利用することが可能です。
この場合は APPLICATION IDENTITY を作成して、データ・ロールにマップします。
-- APPLICATION IDENTITY を作成
CREATE APPLICATION IDENTITY app_id
MAPPED TO 'IAM_OAUTH_CLIENT_ID={{ 統合アプリケーションの CLIENT ID }}';
-- DATA ROLE を作成して DATA GRANT する
CREATE OR REPLACE DATA ROLE app_role;
CREATE OR REPLACE DATA GRANT hr.regions_app_access
AS SELECT ON hr.regions TO app_role;
-- DATA ROLE を APPLICATION IDENTITY に付与
GRANT DATA ROLE app_role TO app_id;
フレームワークとの相性について
アプリケーションの実装面からみた場合、フレームワークが Connection を隠蔽しているケースが多いと思いますので、実際に Deep Data Security を実装しようとした場合、フレームワーク毎にどう対応するか検討が必要です。
例えば Java Persistence API (JPA) だと EntityManager に unwrap() というメソッドがあるので、
Connection con = entityManager.unwrap(java.sql.Connection.class);
と、書くには書けますが、トランザクションを超えてオブジェクトをキャッシュできる実装などもあるので... しっかり検証して下さい!








