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

Keycloak生成JWT的iss声明无效问题及解决方案咨询

Keycloak与Spring Boot在K8s中的Issuer不匹配问题解决

问题背景

在Minikube集群中部署Spring Boot微服务与Keycloak,通过Ingress暴露为localhost访问。使用Postman通过Ingress向Keycloak获取JWT时,Token的iss声明为外部地址localhost/realms/cryptoservices/,但Spring Boot应用配置的JWT issuer是Keycloak内部服务地址http://keycloak-service.default/realms/cryptoservices,导致应用验证Token时报错The iss claim is not valid。

请求流程:

  • Postman通过Ingress向Keycloak发送包含用户名、密码、客户端ID、密钥的认证请求
  • Keycloak返回iss为localhost/realms/cryptoservices/的JWT
  • Postman携带Token访问Spring Boot应用
  • 应用因iss不匹配返回错误响应

相关配置

application.properties

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://keycloak-service.default/realms/cryptoservices
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak-service.default/realms/cryptoservices/protocol/openid-connect/certs

SecurityConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http.oauth2ResourceServer().jwt();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();
        http.authorizeHttpRequests().requestMatchers("/api/v1/generators/**").hasRole("GeneratorsUser");
        http.authorizeHttpRequests().requestMatchers("/api/v1/profiles/**").hasRole("GeneratorsProfilesUser");
        http.authorizeHttpRequests().anyRequest().denyAll();
        return http.build();
    }
}

keycloak.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: default
  labels:
    app: keycloak
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: my_keycloak:latest
        command: ["/opt/keycloak/bin/kc.sh"]
        args: ["start-dev", "--import-realm"]
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
        env:
          - name: KEYCLOAK_ADMIN
            value: admin
          - name: KEYCLOAK_ADMIN_PASSWORD
            value: admin
          - name: KEYCLOAK_PROXY_ADDRESS_FORWARDING
            value: "true"
        volumeMounts:
          - name: keycloak-volume
            mountPath: /opt/keycloak/data/import
      volumes:
        - name: keycloak-volume
          configMap:
            name: keycloak-configmap
 
---

apiVersion: v1
kind: Service
metadata:
  name: keycloak-service
spec:
  selector:
    app: keycloak
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak-ingress
  namespace: default
  annotations:
    kubernetes.io/ingress.class: kong
spec:
  rules:
    - host: localhost
      http:
        paths:
        - path: /admin(/|$)(.*)
          pathType: Prefix
          backend:
            service:
              name: keycloak-service
              port:
                number: 80
        - path: /auth
          pathType: Prefix
          backend:
            service:
              name: keycloak-service
              port:
                number: 80
        - path: /resources(/|$)(.*)
          pathType: Prefix
          backend:
            service:
              name: keycloak-service
              port:
                number: 80
        - path: /realms(/|$)(.*)
          pathType: Prefix
          backend:
            service:
              name: keycloak-service
              port:
                number: 80

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cryptogenerator-ingress
  namespace: generation-service
  annotations:
    kubernetes.io/ingress.class: kong
spec:
  rules:
    - host: localhost
      http:
        paths:
        - path: /cryptogenerator(/|$)(.*)
          pathType: Prefix
          backend:
            service:
              name: my-service
              port:
                number: 80

解决方案

方案一:强制Keycloak生成指定Issuer的JWT

1. 修改Keycloak启动参数

更新Keycloak Deployment的启动参数,添加--hostname=keycloak-service.default和--proxy=edge,确保生成的Token使用内部服务地址作为iss:

# 调整keycloak.yaml中Deployment的args配置
containers:
- name: keycloak
  image: my_keycloak:latest
  command: ["/opt/keycloak/bin/kc.sh"]
  args: ["start-dev", "--import-realm", "--hostname=keycloak-service.default", "--proxy=edge"]
  env:
    - name: KEYCLOAK_ADMIN
      value: admin
    - name: KEYCLOAK_ADMIN_PASSWORD
      value: admin
    - name: KEYCLOAK_PROXY_ADDRESS_FORWARDING
      value: "true"

2. 手动配置Realm的Frontend URL

登录Keycloak管理界面(通过Ingress访问localhost/admin),进入目标Realm:

  • 进入Realm Settings -> General
  • 在Frontend URL中填写http://keycloak-service.default/realms/cryptoservices
  • 保存后,新生成的Tokeniss会使用该地址。

3. 优化Ingress Header传递

在Keycloak Ingress的annotations中添加头传递配置,确保Kong代理正确转发请求信息:

annotations:
  kubernetes.io/ingress.class: kong
  konghq.com/upstream-headers: "X-Forwarded-Host,X-Forwarded-Proto,X-Forwarded-Port"

方案二:Kubernetes内部获取Token(更适合无前端场景)

如果仅需服务间内部调用或本地测试,可绕过Ingress直接访问Keycloak内部服务:

1. 本地测试用Port-Forward

通过minikube将Keycloak内部服务暴露到本地:

minikube service keycloak-service --url

使用该URL获取的Tokeniss为内部服务地址,可直接通过Spring Boot应用验证。

2. 微服务内部直接调用Keycloak

Spring Boot应用若需主动获取Token,直接配置使用Keycloak内部服务地址http://keycloak-service.default/realms/cryptoservices,无需经过Ingress,确保Tokeniss与应用配置一致。

3. 进阶:ServiceAccount集成

配置Keycloak与Kubernetes ServiceAccount集成,让微服务使用自身ServiceAccount Token向Keycloak换取JWT,无需手动管理认证凭据,适合生产环境服务间认证。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 01:17:51