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
相关产品推荐
相关产品推荐

