aFku
aFku

Reputation: 77

"The iss claim is not valid" JWT from keycloak behind ingress

Short description

I am creating whole environment for some microservices. I have REST API written in Java with spring boot. I am also using keycloak as identity provider. I put them both in kubernetes cluster (minikube) and set up ingress to provide external access. To expose ingress as localhost I am using "minikube tunnel". The problem is, every JWT obtained from keycloak through ingress has included issuer with ingress external adress instead of service name that is mapped in ingress resource. In Spring boot application.properties I added jwt-issuer with the name of internal service exposing keycloak deployment. With JWT created in that way I am always getting error "The iss claim is not valid".

Graphical description enter image description here

Requests flow

  1. Postman sends request to keycloak with information: username, password, client ID, client secret
  2. Keycloak sends response with JWT, but with not valid iss claim (is: localhost/realms/cryptoservices/ , should be: keycloak-service.default/realms/cryptoservices)
  3. Postman sends request to access resource from spring boot app with authorization header
  4. App sends error response due to invalid iss claim

Configuration and code

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/protoco l/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

Question

Is there a way to force keycloak to use different issuer when generating JWT? I tried to add X-Forwared headers in ingress and tried to use different flags related to hostname when running keycloak --hostname=, --hostname-strict=false, --proxy edge. Both tries failed.

Or is there any more suitable way to achive similar flow where there is no frontend (besides admin page) and you can easily obtain token from inside kubernetes and then use it to authorize requests for application?

Additional information

Keycloak version: 21.1.1, based on image quay.io/keycloak/keycloak

Upvotes: 1

Views: 2260

Answers (1)

ch4mp
ch4mp

Reputation: 12774

The value of spring.security.oauth2.resourceserver.jwt.issuer-uri must match exactly the value of tokens iss claim (even trailing /, if any is important).

Two options for you:

  • use different hostnames in spring.security.oauth2.resourceserver.jwt.issuer-uri and spring.security.oauth2.resourceserver.jwt.jwk-set-uri: only the host in jwk-set-uri has to be reachable by resource servers. Spring tries to use issuer-uri to fetch OpenID config and then download JWK-set only when jwk-set-uri is not explicitly provided
  • set hostname in Keycloak configuration to match the name with which the service can be reached from the outside of the cluster (this will impact the iss claim), but using something else than loclahost which has a different meaning inside pods and on the host system. You may add an entry for the host machine name in pods hosts file (instead of using localhost).

Upvotes: 4

Related Questions