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

Angular集成Keycloak携带Bearer Token触发CORS及临时头问题

问题背景
  • 依赖版本:keycloak-angular@10.0.1、keycloak-js@18.0.0、Angular@13.0.3
  • 本地Keycloak配置了2个客户端:Angular前端应用、Java Spring REST API后端服务
  • 已验证能力:Angular端通过Keycloak登录可正常生成token,手动将token填入Postman调用Java后端接口可正常返回资源
  • 故障现象:Angular应用直接发起请求调用Java后端接口异常,将enableBearerInterceptor设为true后,浏览器网络面板显示请求为临时头(Provisional headers),面板看似携带了token,但后端实际未收到token,仅收到带CORS预检字段的请求头
  • 调用接口信息:GET方法,地址为localhost:7770/teams

相关代码

Keycloak初始化逻辑(app.module.ts):

function initializeKeycloak(keycloak: KeycloakService): () => Promise<boolean> {
  console.log('keycloak init');
  return () =>
    keycloak.init({
      config: {
        url: 'http://localhost:8080/',
        realm: 'dev',
        clientId: 'frontend'
      },
      initOptions: {
        onLoad: 'check-sso',
        checkLoginIframe: true,
        silentCheckSsoRedirectUri:
          window.location.origin + '/assets/silent-check-sso.html'
      },
      enableBearerInterceptor: false,
      bearerPrefix: 'Bearer',
    }).finally(() => {
      console.log("finally!");
      scriptsService.load('env').then(() => {
        console.log("Loaded env variables:", window["env"])
      }).catch(error =>
        console.warn("Unable to load script: ", error)
      ).finally(() => {
    });
  });
}


{
      provide: APP_INITIALIZER,
      useFactory: initializeKeycloak,
      deps: [KeycloakService],
      multi: true
},

登录落地页请求逻辑:

this.teamsService.fetchTeams().subscribe(value => {
      this.isLoading = false;
      if (value.length != 0) {
        
      }
      this.teams = value;
      this.selectedTeam = this.activeTeamService.getActiveTeam();
    });
fetchTeams(filter?: any): Observable<Team[]> {
    return this.getList(this.TEAMS, filter, this.teamAdapter);
};
protected getList(path, params, adapter): Observable<any> {
    let options = {
      params: params
    }
    return this.http.get(this.urlBase + path, options)
      .pipe(map((data: any[]) => data ? data.map(item => adapter.adapt(item)) : data))
      .pipe(catchError(err => this.error(err)));
  };

后端实际收到的请求头:

host = localhost:7770
connection = keep-alive
pragma = no-cache
cache-control = no-cache
accept = */*
access-control-request-method = GET
access-control-request-headers = access-control-allow-origin,authorization
origin = http://localhost:4200
user-agent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
sec-fetch-mode = cors
sec-fetch-site = same-site
sec-fetch-dest = empty
referer = http://localhost:4200/
accept-encoding = gzip, deflate, br
accept-language = en-US,en;q=0.9

问题根因

你看到的带access-control-request-*字段的请求不是实际的业务GET请求,是浏览器自动发起的CORS预检OPTIONS请求。开启bearer拦截器后,请求会自动附加Authorization自定义头,浏览器判定该请求不属于简单请求,必须先发送OPTIONS预检,等后端返回符合CORS规范、允许携带Authorization头的响应后,才会发送真正带token的GET请求。
故障核心是Spring后端没有正确处理这个OPTIONS预检请求,预检直接被鉴权逻辑拦截或者返回了不符合要求的响应,浏览器直接拦截了后续的真实GET请求,所以网络面板只会显示临时头,后端自然也收不到带token的实际请求。
Postman本身不会触发CORS预检逻辑,所以手动塞token调接口能通,和浏览器场景的行为逻辑完全不一样,不能作为同场景排查依据。


解决方案

按以下步骤排查修复:

  1. 修正Spring后端CORS配置,必须满足以下要求:
    • 允许OPTIONS方法直接放行,不要被鉴权过滤器(Spring Security、Keycloak安全过滤器等)拦截
    • 允许的请求头必须包含Authorization,不要只配置跨域源相关字段
    • 允许的源明确写前端地址http://localhost:4200,不要用*通配符(带身份凭证的跨域请求不允许使用通配符源)
    • 开启允许携带凭证配置
      参考全局CORS配置:
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("http://localhost:4200")
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("*")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    }
    
    如果使用了Spring Security,还要在安全配置里放行所有OPTIONS预检请求:
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            // 其余接口鉴权规则按业务需求配置
            .anyRequest().authenticated();
        return http.build();
    }
    
  2. 检查keycloak-angular拦截器配置,确认后端接口地址localhost:7770没有被加到bearerExcludedUrls排除列表中,不需要额外配置拦截规则的话保持默认即可。
  3. 修复后打开浏览器网络面板验证:先看OPTIONS请求的响应状态是否为200,响应头是否包含Access-Control-Allow-Origin: http://localhost:4200、Access-Control-Allow-Headers: Authorization两个字段,预检通过后会自动发起真正的GET请求,此时GET请求头会正常携带Authorization: Bearer xxx字段,后端可正常解析token返回数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 17:30:49