Saltar al contenido

¿Cómo puedo falsificar una llamada de keycloack para usarla en el desarrollo local?

Te doy la bienvenida a nuestra página web, en este sitio vas a encontrar la resolución de lo que estás buscando.

Solución:

Puede aprovechar el entorno angular (o incluso process.env) variable para cambiar entre implementaciones reales y simuladas.

Aquí hay un ejemplo simple de cómo hacer eso:

app-init.ts

...
import  environment  from '../environments/environment';

export function initializer(
  keycloak: KeycloakService
): () => Promise 

  function authenticate() 
    return keycloak
      .init(
        config:  as any,
        initOptions: onLoad: 'login-required', checkLoginIframe: false,
        bearerExcludedUrls: [],
        loadUserProfileAtStartUp: false
      )
      .then(authenticated => 
        return authenticated ? keycloak.getKeycloakInstance().loadUserInfo() : Promise.reject();
      );
  

  // we use 'any' here so you don't have to define keyCloakUser in each environment    
  const  keyCloakUser  = environment as any; 

  return () => 
    return (keyCloakUser ? Promise.resolve(keyCloakUser) : authenticate()).then(user => 
      // ...
      // do whatever you want with user
      // ...
    );
  ;

environment.ts

export const environment = 
  production: false,
  keyCloakUser: 
    username: '111111111-11',
    name: 'Whatever Something de Paula',
    email: '[email protected]',
  
;

environment.prod.ts

export const environment = 
  production: true,
;

Actualizar

Si quieres burlarte KeycloakService en el lado del cliente, entonces puede decirle a la inyección de dependencia Angular que se encargue de eso:

app.module.ts

import  environment  from '../environments/environment';
import  KeycloakService, KeycloakAngularModule  from 'keycloak-angular';
import  MockedKeycloakService  from './mocked-keycloak.service';

@NgModule(
  ...
  imports: [
    ...
    KeycloakAngularModule
  ],
  providers: [
    
      provide: KeycloakService,
      useClass: environment.production ? KeycloakService : MockedKeycloakService
    ,
    
      provide: APP_INITIALIZER,
      useFactory: initializer,
      multi: true,
      deps: [KeycloakService]
    
  ],
  bootstrap: [AppComponent]
)
export class AppModule  

mocked-keycloak.service.ts

import  Injectable from '@angular/core';
import  KeycloakService  from 'keycloak-angular';

@Injectable()
class MockedKeycloakService extends KeycloakService 
  init() 
    return Promise.resolve(true);
  

  getKeycloakInstance() 
    return 
      loadUserInfo: () => 
        let callback;
        Promise.resolve().then(() => 
          callback(
            userName: 'name'
          );
        );
        return 
          success: (fn) => callback = fn
        ;
      
     as any;
  

Aunque declaras explícitamente que crees que burlarse es la mejor opción, te sugiero que lo reconsideres a favor de configurar una instancia local de Keycloak usando la ventana acoplable. Se vuelve fácil cuando proporciona un dominio para iniciar su entorno. He estado usando este enfoque con éxito durante más de 2 años desarrollando aplicaciones que funcionan con Keycloak. Este enfoque le permitirá “sustituir llamadas a su servidor corporativo”, por lo tanto, lo publico aquí.

Suponiendo que tiene Docker & Docker-compose instalados, necesitará:

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 (el contenido exacto depende de la configuración requerida, este es el mínimo que ha mencionado en su pregunta)


  "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. Cree un entorno Angular dedicado que utilizará “http: // localhost: 8080 / auth” y realm “dev” para su desarrollo local

Las ventajas de este enfoque sobre la burla:

  • todas las funciones de OIDC y keycloak están funcionando. Admito que depende de si los necesita, pero puede usar roles de dominio / cliente, grupos, flujo de OIDC ‘real’ con actualización de token. Esto le da la garantía de que su configuración local funcionará también con el servicio corporativo.
  • esta configuración se puede almacenar en el repositorio (a diferencia de la configuración manual del servidor Keycloak) y se puede utilizar tanto para trabajar en aplicaciones web como en servicios de backend

De forma predeterminada, Keycloak usa una base de datos H2 en memoria y necesita alrededor de 600 MB de RAM, por lo que yo diría que ocupa un espacio relativamente bajo.

Solución

Pude simular el servicio Keycloak usando el método que sugirió @yurzui. Lo documentaré aquí, ya que puede ser útil para alguien.

Inicialmente había publicado una solución en la que exportaba condicionalmente las clases simuladas o reales del módulo simulado. Todo funcionó bien en el modo de desarrollo, pero cuando intenté construir la aplicación para publicar en el servidor de producción, recibí un error, así que tuve que volver a la solución de 2 clases. Explico el problema en detalle esta pregunta.

Este es el código de trabajo (hasta ahora).

Interfaz:

Con un poco de ayuda de la respuesta de @ kev en esta pregunta y @yurzui (nuevamente: D) en esta, creé una clase 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 ; 

luego lo sustituí en 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  

Luego cambié el tipo de variable de servicio de keycloak en app-init, ese fue el único cambio, pero luego pude eliminar la importación de KeycloackService ya que se proporciona en app.module:

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

export function initializer(
    keycloakService: any,
    <...>
): () => Promise {
    return (): Promise => 
        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();
        );
    ;

Pero en el componente todavía tengo que verificar en qué entorno estoy e instanciar la clase correctamente:

<...>
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();
    
    <...>    

Backend:

Eso fue más fácil, nuevamente creé una clase 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; 

Luego sustituí la importación ‘keycloak-connect’ en app.js por esta clase, y todo funcionó bien. Se conecta al servicio real si configuro producción = true y se burla de ello con producción = false.

Solución muy fresca. Si alguien tiene algo que decir sobre mi implementación de la idea de @yurzui, me gustaría saber de usted.

Algunas notas:

  • Todavía no puedo deshacerme de tener que verificar el entorno en la clase del componente principal, como si hiciera esto en el módulo de clase simulada:

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

    app.module ya no funciona. y si hago esto en el componente principal:

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

    La compilación falla con un “KeyclockServiceImpl se refiere a un valor pero se usa como un tipo aquí”;

  • Tuve que exportar todas las clases o la compilación falla.

    export  KeycloakServiceImpl, KeycloakService, MockKeycloakService ; 
    

valoraciones y comentarios

Si guardas algún atascamiento o capacidad de progresar nuestro noticia puedes añadir un exégesis y con gusto lo observaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *