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

Angular集成Keycloak后外部API请求出现401错误求助

问题:Angular集成Keycloak后外部API调用出现401错误

已完成Angular与Keycloak的集成,访问特定页面会重定向至Keycloak登录,登录后可正常浏览页面,但调用无需认证的外部GET请求时返回401错误(提示"Bad credentials")。

相关代码

UserService

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  apiUrl = 'https://api.github.com/users';

  constructor(private http: HttpClient) { }

  getUsers() {
    return this.http
    .get(`${this.apiUrl}?per_page=10`);
  }

  getUser(username: string) {
    return this.http
    .get(`${this.apiUrl}/${username}`);
  }
}

调用服务的组件

import { Component, OnInit } from '@angular/core';
import { UserService } from 'src/app/core/user.service';

@Component({
  selector: 'app-user-list',
  template: `
  <section class="section">
  <div class="container">
  
        <div class="columns is-multiline" *ngIf="users">

          <div class="column is-4" *ngFor="let user of users | async">

            <div class="card">
              <div class="card-content">
                <a routerLink="/users/{{ user.login }}">
                  {{ user.login }}
                </a>
              </div>
            </div>
          </div>

        </div>

  </div>
  </section>
  `,
  styles: []
})
export class UserListComponent implements OnInit {
  users: any;

  constructor(private userService: UserService) { }

  ngOnInit(): void {
    this.users = this.userService.getUsers();
  }
}

错误信息

HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: 'OK', url: 'https://api.github.com/users?per_page=10', ok: false, …}
error:
documentation_url: "https://docs.github.com/rest"
message: "Bad credentials"
[[Prototype]]: Object
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure response for https://api.github.com/users?per_page=10: 401 OK"
name: "HttpErrorResponse"
ok: false
status: 401
statusText: "OK"
url: "https://api.github.com/users?per_page=10"
[[Prototype]]: HttpResponseBase

其他配置代码

AppModule

import { NgModule, APP_INITIALIZER } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ContactComponent } from './contact/contact.component';
import { CoreModule } from './core/core.module'; 
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { initializeKeycloak } from './utility/app.init';
import { AuthGuard } from './utility/app.guard';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    ContactComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule,
    CoreModule,
    KeycloakAngularModule
  ],
  providers: [
    AuthGuard,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      multi: true,
      deps: [KeycloakService],
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

AuthGuard

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

@Injectable({
  providedIn: 'root'
})
export class AuthGuard extends KeycloakAuthGuard {
  constructor(
    protected override readonly router: Router,
    protected readonly keycloak: KeycloakService
  ) {
    super(router, keycloak);
  }

  public async isAccessAllowed(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<boolean> {
    if (!this.authenticated) {
      await this.keycloak.login({
        redirectUri: window.location.origin + state.url
      });
    }

    const requiredRoles = route.data['roles'];

    if (!(requiredRoles instanceof Array) || requiredRoles.length === 0) {
      return true;
    }

    return requiredRoles.every((role) => this.roles.includes(role));
  }
}

Keycloak初始化代码

import { KeycloakService } from 'keycloak-angular';

export function initializeKeycloak(
    keycloak: KeycloakService
    ) {
      return () =>
        keycloak.init({
          config: {
            url: 'http://localhost:8080',
            realm: '[REALM NAME]',
            clientId: '[CLIENT ID NAME]',
          },
          initOptions: {},
          bearerExcludedUrls: ['/assets', 'https://api.github.com/users?per_page=10'],
       });
  }

需求

需要解决两个核心问题:

  • 避免Keycloak自动添加的Authorization Bearer令牌影响外部API调用
  • 保证内部API(如API网关)的正常认证调用

解决方案

问题根源在于bearerExcludedUrls的配置方式错误:当前仅排除了带参数的完整URL,但Keycloak的该配置支持前缀匹配,无需写全路径。修改初始化代码中的bearerExcludedUrls,匹配外部API的前缀即可:

import { KeycloakService } from 'keycloak-angular';

export function initializeKeycloak(
    keycloak: KeycloakService
    ) {
      return () =>
        keycloak.init({
          config: {
            url: 'http://localhost:8080',
            realm: '[REALM NAME]',
            clientId: '[CLIENT ID NAME]',
          },
          initOptions: {},
          // 匹配外部API前缀,该域名下所有请求都不会携带Bearer令牌
          bearerExcludedUrls: ['/assets', 'https://api.github.com/users'],
       });
  }

如果有多个外部API,只需添加对应前缀:

bearerExcludedUrls: ['/assets', 'https://api.github.com/users', 'https://another-external-api.com/v1']

配置后效果:

  • 所有以https://api.github.com/users开头的请求(包括详情请求)都不会携带Keycloak令牌,解决外部API的401问题
  • 内部API请求默认携带令牌,不影响正常认证流程

内容的提问来源于stack exchange,提问作者Shuaib Hussain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 15:06:16