Keycloak生成JWT的iss声明无效问题及解决方案咨询
问题背景
在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 - 保存后,新生成的Token
iss会使用该地址。
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

