<p><strong>Short description</strong></p>
<p>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 &quot;minikube tunnel&quot;. 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 &quot;The iss claim is not valid&quot;.</p>
<p><strong>Graphical description</strong>
<a href="https://i.stack.imgur.com/wW3OC.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/wW3OC.png" alt="enter image description here" /></a></p>
<p><strong>Requests flow</strong></p>
<ol>
<li>Postman sends request to keycloak with information: username, password, client ID, client secret</li>
<li>Keycloak sends response with JWT, but with not valid iss claim (is: localhost/realms/cryptoservices/ , should be: keycloak-service.default/realms/cryptoservices)</li>
<li>Postman sends request to access resource from spring boot app with authorization header</li>
<li>App sends error response due to invalid iss claim</li>
</ol>
<p><strong>Configuration and code</strong></p>
<p><em>application.properties</em></p>
<pre><code>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
</code></pre>
<p><em>SecurityConfig.java</em></p>
<pre><code>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(&quot;/api/v1/generators/**&quot;).hasRole(&quot;GeneratorsUser&quot;);
        http.authorizeHttpRequests().requestMatchers(&quot;/api/v1/profiles/**&quot;).hasRole(&quot;GeneratorsProfilesUser&quot;);
        http.authorizeHttpRequests().anyRequest().denyAll();
        return http.build();
    }
}
</code></pre>
<p><em>keycloak.yaml</em></p>
<pre><code>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: [&quot;/opt/keycloak/bin/kc.sh&quot;]
        args: [&quot;start-dev&quot;, &quot;--import-realm&quot;]
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
        env:
          - name: KEYCLOAK_ADMIN
            value: admin
          - name: KEYCLOAK_ADMIN_PASSWORD
            value: admin
          - name: KEYCLOAK_PROXY_ADDRESS_FORWARDING
            value: &quot;true&quot;
        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
</code></pre>
<p><em>ingress.yaml</em></p>
<pre><code>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
</code></pre>
<p><strong>Question</strong></p>
<p>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.</p>
<p>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?</p>
<p><strong>Additional information</strong></p>
<p>Keycloak version: 21.1.1, based on image quay.io/keycloak/keycloak</p>

aFku
<p>The value of <code>spring.security.oauth2.resourceserver.jwt.issuer-uri</code> must match exactly the value of tokens <code>iss</code> claim (even trailing <code>/</code>, if any is important).</p>
<p>Two options for you:</p>
<ul>
<li>use different hostnames in <code>spring.security.oauth2.resourceserver.jwt.issuer-uri</code> and <code>spring.security.oauth2.resourceserver.jwt.jwk-set-uri</code>: only the host in <code>jwk-set-uri</code> has to be reachable by resource servers. Spring tries to use <code>issuer-uri</code> to fetch OpenID config and then download JWK-set only when <code>jwk-set-uri</code> is not explicitly provided</li>
<li>set <code>hostname</code> in Keycloak <a href="https://www.keycloak.org/server/hostname" rel="nofollow noreferrer">configuration</a> to match the name with which the service can be reached from the outside of the cluster (this will impact the <code>iss</code> claim), but using something else than <code>loclahost</code> which has a different meaning inside pods and on the host system. You may <a href="https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/" rel="nofollow noreferrer">add an entry for the host machine name in pods hosts file</a> (instead of using <code>localhost</code>).</li>
</ul>
