You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular集成Keycloak登录后请求Token返回401错误求助

Angular 15 + Keycloak 20 集成后Token请求被拒绝问题分析

问题现象

首次尝试集成Angular 15与Keycloak 20,应用可正常重定向至Keycloak登录页面,登录后重定向回应用却显示空白页。浏览器控制台提示请求https://my.keycloack.domain/realms/myRealm/protocol/openid-connect/token返回401状态码;Keycloak日志显示CODE_TO_TOKEN_ERROR,错误类型为invalid_client_credentials,但已确认用户登录密码正确。

应用配置代码

app.module.ts中的Keycloak初始化代码

import { HttpClient, HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { RouteReuseStrategy } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CalendarModule, DateAdapter } from 'angular-calendar';
import { adapterFactory } from 'angular-calendar/date-adapters/date-fns';
import {NgxPaginationModule} from 'ngx-pagination';
import { NgxGoogleAnalyticsModule } from 'ngx-google-analytics';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment.prod';
import { KeycloakService } from 'keycloak-angular';
import { LoginInfo } from '../main';

const initializeKeycloak = (keycloak: KeycloakService) => async () =>
    keycloak.init({
      config: {
        url: (JSON.parse(localStorage.getItem('loginInfo')) as LoginInfo).baseUrl,
        realm: (JSON.parse(localStorage.getItem('loginInfo')) as LoginInfo).realm,
        clientId: (JSON.parse(localStorage.getItem('loginInfo')) as LoginInfo).client
      },
      initOptions: {
        onLoad: 'check-sso',
        silentCheckSsoRedirectUri:
          window.location.origin + '/assets/silent-check-sso.html'
      },
      shouldAddToken: (request) => {
        const { method, url } = request;

        const isGetRequest = 'GET' === method.toUpperCase();
        const acceptablePaths = ['/assets', '/clients/public'];
        const isAcceptablePathMatch = acceptablePaths.some((path) => url.includes(path));

        return !(isGetRequest && isAcceptablePathMatch);
      }
    });
@NgModule({
    declarations: [AppComponent],
    // eslint-disable-next-line max-len
    imports: [FormsModule, BrowserModule, IonicModule.forRoot(), AppRoutingModule, HttpClientModule, BrowserAnimationsModule, CalendarModule.forRoot({ provide: DateAdapter, useFactory: adapterFactory }), NgxPaginationModule,
        NgxGoogleAnalyticsModule.forRoot(environment.ga4MeasurementId)],
    providers: [KeycloakService,
      {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService]
    }, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
    bootstrap: [AppComponent]
})
export class AppModule {}

/assets/silent-check-sso.html文件

<html>
  <body>
    <script>
      parent.postMessage(location.href, location.origin);
    </script>
  </body>
</html>

route-guard.service.ts路由守卫代码

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, Router } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';

@Injectable({
  providedIn: 'root'
})
export class RouteGuardService extends KeycloakAuthGuard implements CanActivate {

  constructor(
    protected readonly router: Router,
    protected readonly keycloak: KeycloakService) {
      super(router, keycloak);
    }

  public async isAccessAllowed(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) {
    // Force the user to log in if currently unauthenticated.
    if (!this.authenticated) {
      await this.keycloak.login({
        redirectUri: window.location.origin + state.url
      });
    }

    // Get the roles required from the route.
    const requiredRoles = route.data.roles;

    // Allow the user to proceed if no additional roles are required to access the route.
    if (!(requiredRoles instanceof Array) || requiredRoles.length === 0) {
      return true;
    }

    // Allow the user to proceed if all the required roles are present.
    return requiredRoles.every((role) => this.roles.includes(role));
  }
}

app-routing.module.ts路由配置

import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';

import { RouteGuardService } from './guard/route-guard.service';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'prenotazioni',
    pathMatch: 'full',
  },
  {
    path: 'prenotazioni',
    loadChildren: () => import('./prenotazioni/prenotazioni.module').then( m => m.PrenotazioniPageModule),
    canActivate: [RouteGuardService]
  },
  {
    path: 'agenda',
    loadChildren: () => import('./agenda/agenda.module').then( m => m.AgendaPageModule),
    canActivate: [RouteGuardService]
  },
  {
    path: 'configuratore',
    loadChildren: () => import('./configuratore/configuratore.module').then( m => m.ConfiguratorePageModule),
    canActivate: [RouteGuardService]
  },
  {
    path: 'login',
    loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
  },
  {
    path: 'customer-sat-page',
    loadChildren: () => import('./customer-sat-page/customer-sat-page.module').then( m => m.CustomerSatPagePageModule)
  },
];
@NgModule({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

排查方向及解决方法

  • 客户端访问类型配置错误
    Angular是前端单页应用,无法安全存储客户端密钥,需确保Keycloak客户端的Access Type设置为public。若设置为confidential,Keycloak会要求请求token时携带客户端密钥,前端无法提供有效密钥,直接触发invalid_client_credentials错误。

  • 重定向URI未添加至允许列表
    检查Keycloak客户端设置中的Valid Redirect URIs,确认包含应用的完整域名及路径(如https://your-app-domain/*),同时需包含silent-check-sso.html的地址(如https://your-app-domain/assets/silent-check-sso.html)。重定向URI不在允许列表中时,Keycloak会拒绝token交换请求。

  • 客户端ID不匹配
    验证localStorage.getItem('loginInfo')获取的clientId与Keycloak后台配置的客户端ID完全一致,注意大小写、特殊字符,ID不匹配会被判定为无效客户端凭证。

  • 初始化配置冗余参数
    确认keycloak.init的配置中未添加credentials参数,public类型客户端无需提供客户端密钥,多余的凭证参数会导致请求验证失败。

内容的提问来源于stack exchange,提问作者Lucio Crusca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 23:22:09