离子应用程序的 Keycloak:带有 cordova-native 的 keycloak-js 不起作用

2024-02-10

我正在尝试在我的 ionic(4) cordova 应用程序中使用 Keycloak-js(来自 4.4.0.Final)库。 我已遵循example https://github.com/keycloak/keycloak/tree/master/examples/cordova-native和指示文档 https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter。 我已经安装了cordova-plugin-browsertab, cordova-plugin-deeplinks, cordova-plugin-inappbrowser. Added <preference name="AndroidLaunchMode" value="singleTask" /> in my config.xml这就是我对 config.xml 的修改的样子。

<widget id="org.phidatalab.radar_armt"....>

<plugin name="cordova-plugin-browsertab" spec="0.2.0" />
<plugin name="cordova-plugin-inappbrowser" spec="3.0.0" />
<plugin name="cordova-plugin-deeplinks" spec="1.1.0" />
<preference name="AndroidLaunchMode" value="singleTask" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<universal-links>
    <host name="keycloak-cordova-example.exampledomain.net" scheme="https">
        <path event="keycloak" url="/login" />
    </host>
</universal-links>
</widget>

和我的服务使用Keycloak-js如下所示。

static init(): Promise<any> {
  // Create a new Keycloak Client Instance
  let keycloakAuth: any = new Keycloak({
      url: 'https://exampledomain.net/auth/',
      realm: 'mighealth',
      clientId: 'armt',

  });

    return new Promise((resolve, reject) => {
      keycloakAuth.init({
          onLoad: 'login-required',
          adapter: 'cordova-native',
          responseMode: 'query',
          redirectUri: 'android-app://org.phidatalab.radar_armt/https/keycloak-cordova-example.github.io/login'
      }).success(() => {

          console.log("Success")
          resolve();
        }).error((err) => {
          reject(err);
        });
    });
  }

我可以成功构建并运行应用程序Android。然而,这不起作用。 从adb我得到的日志(对于两者cordova and cordova-native适配器)

12-04 19:07:35.911 32578-32578/org.phidatalab.radar_armt D/SystemWebChromeClient: ng:///AuthModule/EnrolmentPageComponent.ngfactory.js: Line 457 : ERROR
12-04 19:07:35.911 32578-32578/org.phidatalab.radar_armt I/chromium: [INFO:CONSOLE(457)] "ERROR", source: ng:///AuthModule/EnrolmentPageComponent.ngfactory.js (457)
12-04 19:07:35.918 32578-32578/org.phidatalab.radar_armt D/SystemWebChromeClient: ng:///AuthModule/EnrolmentPageComponent.ngfactory.js: Line 457 : ERROR CONTEXT
12-04 19:07:35.919 32578-32578/org.phidatalab.radar_armt I/chromium: [INFO:CONSOLE(457)] "ERROR CONTEXT", source: ng:///AuthModule/EnrolmentPageComponent.ngfactory.js (457)

如果我尝试在浏览器上运行它,我会得到"universalLink is undefined".

我真的需要一些帮助才能使其正常工作。我缺少什么?非常感谢任何形式的帮助。 或者是否有解决方法/示例让 keycloak 为离子(公共)客户端工作?


我在这里发布我的解决方案,因为我浪费了很多时间来获取适用于我的环境的可用插件。提供的实现keycloak-js已经相当过时了。因此,如果您尝试将它用于 ionic-3 应用程序,它就不起作用。

我的解决方案是使用InAppBrowser插件(类似于cordova的方法keycloak-js)并遵循标准 Oauth2authorization_code程序。我查看了代码keycloak-js并基于它实施了解决方案。谢谢keycloak-js too.

这里是。 第一步:安装[cordova-inapp-browser][1].

第二步:样本keycloak-auth.service.ts可能如下所示。这可能会取代 keycloak-js,但仅限于cordova选项。

import 'rxjs/add/operator/toPromise'

import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'
import {Injectable} from '@angular/core'
import {JwtHelperService} from '@auth0/angular-jwt'
import {StorageService} from '../../../core/services/storage.service'
import {StorageKeys} from '../../../shared/enums/storage'
import {InAppBrowser, InAppBrowserOptions} from '@ionic-native/in-app-browser';


const uuidv4 = require('uuid/v4');

@Injectable()
export class AuthService {
  URI_base: 'https://my-server-location/auth';
  keycloakConfig: any;

  constructor(
    public http: HttpClient,
    public storage: StorageService,
    private jwtHelper: JwtHelperService,
    private inAppBrowser: InAppBrowser,
  ) {
      this.keycloakConfig = {
        authServerUrl: 'https://my-server-location/auth/', //keycloak-url
        realm: 'myrealmmName', //realm-id
        clientId: 'clientId', // client-id
        redirectUri: 'http://my-demo-app/callback/',  //callback-url registered for client.
                                                      // This can be anything, but should be a valid URL
      };
  }

  public keycloakLogin(login: boolean): Promise<any> {
    return new Promise((resolve, reject) => {
      const url = this.createLoginUrl(this.keycloakConfig, login);

      const options: InAppBrowserOptions = {
        zoom: 'no',
        location: 'no',
        clearsessioncache: 'yes',
        clearcache: 'yes'
      }
      const browser = this.inAppBrowser.create(url, '_blank', options);

      const listener = browser.on('loadstart').subscribe((event: any) => {
        const callback = encodeURI(event.url);
        //Check the redirect uri
        if (callback.indexOf(this.keycloakConfig.redirectUri) > -1) {
          listener.unsubscribe();
          browser.close();
          const code = this.parseUrlParamsToObject(event.url);
          this.getAccessToken(this.keycloakConfig, code).then(
            () => {
              const token = this.storage.get(StorageKeys.OAUTH_TOKENS);
              resolve(token);
            },
            () => reject("Count not login in to keycloak")
          );
        }
      });

    });
  }

  parseUrlParamsToObject(url: any) {
    const hashes = url.slice(url.indexOf('?') + 1).split('&');
    return hashes.reduce((params, hash) => {
      const [key, val] = hash.split('=');
      return Object.assign(params, {[key]: decodeURIComponent(val)})
    }, {});
  }

  createLoginUrl(keycloakConfig: any, isLogin: boolean) {
    const state = uuidv4();
    const nonce = uuidv4();
    const responseMode = 'query';
    const responseType = 'code';
    const scope = 'openid';
    return this.getUrlForAction(keycloakConfig, isLogin) +
      '?client_id=' + encodeURIComponent(keycloakConfig.clientId) +
      '&state=' + encodeURIComponent(state) +
      '&redirect_uri=' + encodeURIComponent(keycloakConfig.redirectUri) +
      '&response_mode=' + encodeURIComponent(responseMode) +
      '&response_type=' + encodeURIComponent(responseType) +
      '&scope=' + encodeURIComponent(scope) +
      '&nonce=' + encodeURIComponent(nonce);
  }

  getUrlForAction(keycloakConfig: any, isLogin: boolean) {
    return isLogin ? this.getRealmUrl(keycloakConfig) + '/protocol/openid-connect/auth'
      : this.getRealmUrl(keycloakConfig) + '/protocol/openid-connect/registrations';
  }

  loadUserInfo() {
    return this.storage.get(StorageKeys.OAUTH_TOKENS).then( tokens => {
      const url = this.getRealmUrl(this.keycloakConfig) + '/protocol/openid-connect/userinfo';
      const headers = this.getAccessHeaders(tokens.access_token, 'application/json');
      return this.http.get(url, {headers: headers}).toPromise();
    })
  }

  getAccessToken(kc: any, authorizationResponse: any) {
    const URI = this.getTokenUrl();
    const body = this.getAccessTokenParams(authorizationResponse.code, kc.clientId, kc.redirectUri);
    const headers = this.getTokenRequestHeaders();

    return this.createPostRequest(URI,  body, {
      header: headers,
    }).then((newTokens: any) => {
      newTokens.iat = (new Date().getTime() / 1000) - 10; // reduce 10 sec to for delay
      this.storage.set(StorageKeys.OAUTH_TOKENS, newTokens);
    });
  }

  refresh() {
    return this.storage.get(StorageKeys.OAUTH_TOKENS)
      .then(tokens => {
        const decoded = this.jwtHelper.decodeToken(tokens.access_token)
        if (decoded.iat + tokens.expires_in < (new Date().getTime() /1000)) {
          const URI = this.getTokenUrl();
          const headers = this.getTokenRequestHeaders();
          const body = this.getRefreshParams(tokens.refresh_token, this.keycloakConfig.clientId);
          return this.createPostRequest(URI, body, {
            headers: headers
          })
        } else {
          return tokens
        }
      })
      .then(newTokens => {
        newTokens.iat = (new Date().getTime() / 1000) - 10;
        return this.storage.set(StorageKeys.OAUTH_TOKENS, newTokens)
      })
      .catch((reason) => console.log(reason))
  }

  createPostRequest(uri, body, headers) {
    return this.http.post(uri, body, headers).toPromise()
  }

  getAccessHeaders(accessToken, contentType) {
    return new HttpHeaders()
      .set('Authorization', 'Bearer ' + accessToken)
      .set('Content-Type', contentType);
  }

  getRefreshParams(refreshToken, clientId) {
    return new HttpParams()
      .set('grant_type', 'refresh_token')
      .set('refresh_token', refreshToken)
      .set('client_id', encodeURIComponent(clientId))
  }

  getAccessTokenParams(code , clientId, redirectUrl) {
    return new HttpParams()
      .set('grant_type', 'authorization_code')
      .set('code', code)
      .set('client_id', encodeURIComponent(clientId))
      .set('redirect_uri', redirectUrl);
  }

  getTokenUrl() {
    return this.getRealmUrl(this.keycloakConfig) + '/protocol/openid-connect/token';
  }

  getTokenRequestHeaders() {
    const headers = new HttpHeaders()
      .set('Content-Type', 'application/x-www-form-urlencoded');

    const clientSecret = (this.keycloakConfig.credentials || {}).secret;
    if (this.keycloakConfig.clientId && clientSecret) {
      headers.set('Authorization', 'Basic ' + btoa(this.keycloakConfig.clientId + ':' + clientSecret));
    }
    return headers;
  }

  getRealmUrl(kc: any) {
    if (kc && kc.authServerUrl) {
      if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') {
        return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm);
      } else {
        return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm);
      }
    } else {
      return undefined;
    }
  }
}

第 3 步:然后您可以在组件中根据需要使用此服务。

@Component({
  selector: 'page-enrolment',
  templateUrl: 'enrolment-page.component.html'
})
export class EnrolmentPageComponent {
constructor(
    public storage: StorageService,
    private authService: AuthService,
  ) {}
  goToRegistration() {
    this.loading = true;
    this.authService.keycloakLogin(false)
      .then(() => {
        return this.authService.retrieveUserInformation(this.language)
      });
  }
}

注意:keycloakLogin(true) 带您进入登录页面,keycloakLogin(false) 带您进入keycloak 注册页面。

我希望这或多或少能帮助你解决这个问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

离子应用程序的 Keycloak:带有 cordova-native 的 keycloak-js 不起作用 的相关文章

随机推荐