web-dev-qa-db-ja.com

ローカル開発で使用するキークローク呼び出しを偽造するにはどうすればよいですか?

私の会社はLDAPに接続された認証にKeycloakを使用し、企業データで満たされたユーザーオブジェクトを返しています。しかし、この期間中、私たちは全員自宅で仕事をしており、毎日の仕事では、アプリをリロードするたびに社内サーバーで認証する必要があるため、オーバーヘッドが高くつくことが証明されています。特に断続的なインターネット接続で。

Keycloakの呼び出しを偽造し、keycloak.protect()を正常に機能させるにはどうすればよいですか?

私は自分のマシンにKeyclockサーバーをインストールできますが、それはその中で実行されている別のサーバー、つまりvagrant VM、Postgresサーバー、サーバーなど、私が開いたままにしておくものだからです。モックコールを実行し、ハードコードされた固定オブジェクトを返すのが最善です。

私のプロジェクトのapp-init.tsはこれです:

_import { KeycloakService } from 'keycloak-angular';
import { KeycloakUser } from './shared/models/keycloakUser';
<...>

export function initializer(
    keycloak: KeycloakService,
    <...>
): () => Promise<any> {
    return (): Promise<any> => {
        return new Promise(async (res, rej) => {
            <...>    
            await keycloak.init({
                config: environment.keycloakConfig,
                initOptions: {
                    onLoad: 'login-required',
                    // onLoad: 'check-sso',
                    checkLoginIframe: false
                },
                bearerExcludedUrls: [],
                loadUserProfileAtStartUp: false
            }).then((authenticated: boolean) => {
                if (!authenticated) return;
                keycloak.getKeycloakInstance()
                    .loadUserInfo()
                    .success(async (user: KeycloakUser) => {
                       // ...
                       // load authenticated user data
                       // ...
                    })    
            }).catch((err: any) => rej(err));
            res();
        });
    };
_

私は1人の固定ログインユーザーが必要です。ただし、修正されたカスタマイズされたデータを返す必要があります。このようなもの:

_{ username: '111111111-11', name: 'Whatever Something de Paula',
  email: '[email protected]', department: 'sales', employee_number: 7777777 }
_

[〜#〜]編集[〜#〜]

@BojanKogojのアイデアを確認しようとしましたが、Angular Interceptorページと他の例とチュートリアルからのAFAIUです。コンポーネントに挿入する必要があります。Keycloakの初期化は、コンポーネントです。また、Keycloakのリターンはinit()メソッドの直接のリターンではありません。.getKeycloakInstance().loadUserInfo().success()シーケンス内の他のオブジェクトを通過します。または、それが完全に理解できなかったのは私だけかもしれません。呼び出しをインターセプトして正しい結果を返すことができるインターセプターの例を使用すると、それは可能性があります。

Edit2

それを補完するために必要なのは、キークロークのシステム全体が機能することです。 _(user: KeycloakUser) => {_関数がkeycloakの内部システムのsuccessメソッドに渡されることに注意してください。上記で述べたように、ルートにはkeycloak.protect()があり、機能する必要があります。つまり、ユーザーとのプロミスを返すという単純なケースではありません。 .getKeycloakInstance()。loadUserInfo()。success()チェーン全体をモックする必要があります。または、少なくとも私はそれを理解しています。

@ yurzuiの回答に基づいて作成したソリューションに回答を含めました

誰かがさらに良い解決策を思い付くことができるかどうかを確認するために賞金を授与するために数日待ちます(私は疑います)。

10
Nelson Teixeira

モッキングが最良の選択肢であると考えていることを明確に述べていますが、Dockerを使用してローカルのKeycloakインスタンスを設定することをお勧めします。 bootstrap your environment。コーポレートサーバーへの呼び出し」ので、ここに投稿します。

Docker&docker-composeがインストールされていると仮定すると、次のものが必要です。

1。 docker-compose.yaml

version: '3.7'

services:
  keycloak:
    image: jboss/keycloak:10.0.1
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: admin
      KEYCLOAK_IMPORT: /tmp/dev-realm.json
    ports:
      - 8080:8080
    volumes:
      - ./dev-realm.json:/tmp/dev-realm.json

2。 dev-realm.json(正確な内容は必要な設定によって異なります。これは質問で述べた最小値です)

{
  "id": "dev",
  "realm": "dev",
  "enabled": true,
  "clients": [
    {
      "clientId": "app",
      "enabled": true,
      "redirectUris": [
        "*"
      ],
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "secret": "mysecret",
      "publicClient": false,
      "protocol": "openid-connect",
      "fullScopeAllowed": false,
      "protocolMappers": [
        {
          "name": "department",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "user.attribute": "department",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "department",
            "userinfo.token.claim": "true"
          }
        },
        {
          "name": "employee_number",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "user.attribute": "employee_number",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "employee_number",
            "userinfo.token.claim": "true"
          }
        }
      ]
    }
  ],
  "users": [
    {
      "username": "111111111-11",
      "enabled": true,
      "firstName": "Whatever Something de Paula",
      "email": "[email protected]",
      "credentials": [{
        "type": "password",
        "value": "demo"
      }],
      "attributes": {
        "department": "sales",
        "employee_number": 7777777
      }
    }
  ]
}

3。ローカル開発用に " http:// localhost:8080/auth "とレルム "dev"を使用する専用のAngular環境を作成します

このアプローチのモックに対する利点:

  • すべてのOIDCおよびキークローク機能が動作しています。それらが必要かどうかによって異なりますが、レルム/クライアントの役割、グループ、トークンの更新を伴う「実際の」OIDCフローを自由に使用できます。これにより、ローカル設定が企業サービスでも機能することが保証されます
  • この設定はリポジトリに保存でき(Keycloakサーバーの手動設定とは異なり)、Webアプリケーションとバックエンドサービスの両方で使用できます。

デフォルトでは、KeycloakはH2インメモリーデータベースを使用し、約600MBのRAMが必要であるため、フットプリントが比較的低いと主張します。

2
dchrzascik

ソリューション

@yurzuiが提案する方法を使用して、Keycloakサービスをモックすることができました。誰かに役立つかもしれないので、ここでドキュメント化します。

最初は、モックモジュールからモックまたは実際のクラスを条件付きでエクスポートするソリューションを投稿していました。開発モードではすべてうまくいきましたが、本番サーバーで公開するためのアプリケーションをビルドしようとするとエラーが発生したため、2クラスのソリューションに戻る必要がありました。問題を詳しく説明します this 質問。

これは(これまでのところ)作業コードです。

フロントエンド:

this 質問の@kevの回答と this oneの@yurzui(ここでも:D)の助けを借りて、MockKeycloakServiceクラスを作成しました。

import { Injectable } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { environment } from '../../../environments/environment';

@Injectable({ providedIn: 'root' })
export default class MockKeycloakService { 

    init() {
        console.log('[KEYCLOAK] Mocked Keycloak call');
        return Promise.resolve(true);
    }

    getKeycloakInstance() {
        return {
            loadUserInfo: () => {
                let callback : any;
                Promise.resolve().then(() => {
                    callback({
                        username: '77363698953',
                        NOME: 'Nelson Teixeira',
                        FOTO: 'assets/usuarios/nelson.jpg',
                        LOTACAOCOMPLETA: 'DIOPE/SUPOP/OPSRL/OPSMC (local)',
                    });
                });
                return { success: fn=>callback = fn };
            }
        } as any;
    }

    login() {}  
    logout() {}
} 

const KeycloakServiceImpl =
  environment.production ? KeycloakService : MockKeycloakService

export { KeycloakServiceImpl, KeycloakService, MockKeycloakService }; 

それから私はそれをapp.moduleで置き換えました:

<...>
import { KeycloakAngularModule } from 'keycloak-angular';
import { KeycloakServiceImpl } from 'src/app/shared/services/keycloak-mock.service';
import { initializer } from './app-init';
<...>

    imports: [
        KeycloakAngularModule,
         <...>  
    ],
    providers: [
        <...>,
        {
            provide: APP_INITIALIZER,
            useFactory: initializer,
            multi: true,
            deps: [KeycloakServiceImpl, <...>]
        },
        <...>
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

次に、app-initでkeycloakサービス変数のタイプを変更しましたが、それが唯一の変更でしたが、app.moduleで提供されているKeycloackServiceインポートを削除できました。

import { KeycloakUser } from './shared/models/keycloakUser';
<...>

export function initializer(
    keycloakService: any,
    <...>
): () => Promise<any> {
    return (): Promise<any> => {
        return new Promise(async (res, rej) => {
            <...>    
            await keycloak.init({
                config: environment.keycloakConfig,
                initOptions: {
                    onLoad: 'login-required',
                    // onLoad: 'check-sso',
                    checkLoginIframe: false
                },
                bearerExcludedUrls: [],
                loadUserProfileAtStartUp: false
            }).then((authenticated: boolean) => {
                if (!authenticated) return;
                keycloak.getKeycloakInstance()
                    .loadUserInfo()
                    .success(async (user: KeycloakUser) => {

                        <...>

                    })    
            }).catch((err: any) => rej(err));
            res();
        });
    };

しかし、コンポーネントでは、私がいる環境をチェックし、クラスを正しくインスタンス化する必要があります:

<...>
import { MockKeycloakService } from '../../shared/services/keycloak.mock.service';
import { environment } from '../../../environments/environment';    
<...>

export class MainComponent implements OnInit, OnDestroy {
    <...>
    keycloak: any;

    constructor(
        <...>           
    ) {
        this.keycloak = (environment.production) ? KeycloakServiceImpl : new KeycloakServiceImpl();
  }

    async doLogout() {
        await this.keycloak.logout();
    }

    async doLogin() {
        await this.keycloak.login();
    }
    <...>    
}

バックエンド:

それはより簡単でした、再び私はKeycloakMockクラスを作成しました:

import KeyCloack from 'keycloak-connect';

class KeycloakMock {

    constructor(store, config) {
        //ignore them
    }

    middleware() {
        return (req, res, next) =>{ 
        next();
    }}

    protect(req, res, next) {
        return (req, res, next) =>{ 
        next();
    }}
}

const exportKeycloak = 
    (process.env.NODE_ENV == 'local') ? KeycloakMock : KeyCloack;

export default exportKeycloak; 

次に、このクラスをapp.jsの「keycloak-connect」インポートに置き換えただけで、everthigは正常に機能しました。 production = trueに設定すると、実際のサービスに接続し、production = falseでモックします。

非常にクールなソリューション。 @yurzuiアイデアの私の実装について誰かが言うことがあれば、私はあなたから聞いてみたいと思います。

いくつかのメモ:

  • 私はまだモッククラスモジュールでこれを行うかのように、メインコンポーネントクラスの環境をチェックする必要を取り除くことができませんでした。

    const KeycloakServiceImpl = 
        environment.production ? KeycloakService : new MockKeycloakService()
    

    app.moduleは動作しなくなりました。そして私がメインコンポーネントでこれを行うと:

    constructor(
            <...>
            keycloakService: KeyclockServiceImpl;
        ) {  }
    

    ビルドは「KeyclockServiceImplは値を参照していますが、ここではタイプとして使用されています」で失敗します。

  • すべてのクラスをエクスポートする必要があった、またはビルドが失敗した

    export { KeycloakServiceImpl, KeycloakService, MockKeycloakService }; 
    
1
Nelson Teixeira