title: Spring Boot issue with Keycloak in Docker Compose
body: <p>I'm having trouble getting Keycloak to work in docker compose. I have a basic Spring Boot REST API that uses the new OAuth stack from Spring Security to work as a resource server. I've set up the Keycloak authentication server to import a realm on first start up. I'm trying to use the client-credentials flow. It works fine when the API is run locally, but when I run it in docker-compose, it fails with a 401 Unauthorized before it even reaches the endpoint. From what I've read, it is potentially a cors error in that the preflight request is what triggers the 401, but none of the configuration I've played with has worked. When I try to access with curl, the response indicates that it's an invalid token. I've looked into that too for some common issues like timezone drift and whatnot, but also doesn't seem to be the culprit. I've updated my <code>etc/hosts</code> file so I can change the <code>iss</code> claim to be <code>keycloak:8081</code> but that doesn't help either. To be clear, I can log in no problem, and if I turn off the <code>.authorizeRequests().anyRequest().authenticated().and().oauth2ResourceServer().jwt()</code> it also works.</p>
  <pre><code>version: &quot;3.8&quot;
  
  services:
    mariadb:
      image: mariadb:10.5.3
      environment:
        MYSQL_ROOT_PASSWORD: root_password
        MYSQL_DATABASE: keycloak
        MYSQL_USER: some_user
        MYSQL_PASSWORD: password
      container_name: mariadb-10.5.3
      networks:
        - app
      volumes:
        - &quot;auth:/var/lib/mysql&quot;
  
    auth:
      build:
        context: ./auth/
        args:
          - JAVA_VERSION=14
      image: auth:1.0.0.1
      container_name: auth-1.0.0.1
      ports:
        - &quot;8081:8081&quot;
      environment:
        KEYCLOAK_DB_PROTOCOL: mysql
        KEYCLOAK_DB_HOST: mariadb
        KEYCLOAK_DB_PORT: 3306
        KEYCLOAK_DB_NAME: keycloak
        KEYCLOAK_DB_USERNAME: some_user
        KEYCLOAK_DB_PASSWORD: password
        KEYCLOAK_DB_DRIVER: org.mariadb.jdbc.Driver
        KEYCLOAK_CONTEXT_PATH: /auth
      depends_on:
        - mariadb
      entrypoint:
        [&quot;./wait-for-it.sh&quot;, &quot;mariadb:3306&quot;, &quot;--&quot;, &quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]
      networks:
        - app
  
    api:
      build:
        context: ./api/
        args:
          - JAVA_VERSION=14
      image: api:1.0.0.1
      container_name: api-1.0.0.1
      ports:
        - &quot;8080:8080&quot;
      environment:
        KEYCLOAK_HOST: keycloak
        KEYCLOAK_PORT: 8081
        KEYCLOAK_CONTEXT_PATH: /auth
        KEYCLOAK_REALM: api
        ELASTICSEARCH_HOST: elasticsearch
        ELASTICSEARCH_PORT: 9200
      depends_on:
        - elasticsearch
        - auth
      entrypoint:
        [
          &quot;./wait-for-it.sh&quot;,
          &quot;elasticsearch:9200&quot;,
          &quot;--&quot;,
          &quot;java&quot;,
          &quot;-jar&quot;,
          &quot;/app.jar&quot;,
        ]
      networks:
        - app
  
    elasticsearch:
      image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.7.1
      container_name: elasticsearch-7.7.1
      environment:
        discovery.type: single-node
      ports:
        - &quot;9200:9200&quot;
        - &quot;9300:9300&quot;
      networks:
        - app
        - elk
  
    kibana:
      image: docker.elastic.co/kibana/kibana-oss:7.7.1
      container_name: kibana-7.7.1
      depends_on:
        - elasticsearch
      ports:
        - &quot;5601:5601&quot;
      networks:
        - elk
  
    logstash:
      build:
        context: ./elk/logstash/
      image: logstash:7.7.1
      container_name: logstash-7.7.1
      depends_on:
        - elasticsearch
      environment:
        PIPELINE_WORKERS: 1
      networks:
        - elk
  
  networks:
    elk:
      name: elk
    app:
      name: app
  
  volumes:
    auth:
      name: auth
  </code></pre>
  <pre class="lang-java prettyprint-override"><code>@Configuration
  @EnableGlobalMethodSecurity(prePostEnabled = true)  // enables PreAuthorize annotation
  public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.cors().and()
            .authorizeRequests()
              .anyRequest()
                .authenticated()
          .and()
            .oauth2ResourceServer()
              .jwt();
    }
  }
  </code></pre>
  <p>api - application.properties</p>
  <pre class="lang-java prettyprint-override"><code>spring.security.oauth2.resourceserver.jwt.issuer-uri=http://\${KEYCLOAK_HOST:localhost}:\${KEYCLOAK_PORT}/\${KEYCLOAK_CONTEXT_PATH:auth}/realms/\${KEYCLOAK_REALM}
  spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://\${KEYCLOAK_HOST:localhost}:\${KEYCLOAK_PORT}/\${KEYCLOAK_CONTEXT_PATH:auth}/realms/\${KEYCLOAK_REALM}/protocol/openid-connect/certs
  </code></pre>
  <p>keycloak - application.yaml</p>
  <pre><code>keycloak:
    cors: true
    server:
      contextPath: ${KEYCLOAK_CONTEXT_PATH:/auth}
      adminUser:
        username: admin
        password: admin
      realmImportFile:
        - api-realm.json
  </code></pre>
  <p>controller</p>
  <pre><code>@RestController
  @RequestMapping(&quot;/&quot;)
  public class Controller {
    @Autowired
    IDocumentRepository documentRepository;
  
    @GetMapping(&quot;/{id}&quot;)
    @PreAuthorize(&quot;@authorizationService.checkClientIDMatchesID(#token, #id)&quot;)
    public Iterable&lt;Documents&gt; getAll(@AuthenticationPrincipal Jwt token, @PathVariable(&quot;id&quot;) String id) {
      return documentRepository.findById(id);
    }
  }
  </code></pre>
  <p>custom authorization method</p>
  <pre><code>@Service
  public class AuthorizationService {
    private static final Logger logger = LoggerFactory.getLogger(AuthorizationService.class);
  
    public boolean checkClientIDMatchesID(Jwt token, String id)
    {
      if (id == null || token == null || token.getClaimAsBoolean(&quot;clientId&quot;) == null) return false;
      return ((String)token.getClaim(&quot;clientId&quot;)).equalsIgnoreCase(id);
    }
  }
  </code></pre>
  <p>Here are the logs for the API:
  <a href="https://i.stack.imgur.com/rlkFA.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/rlkFA.png" alt="enter image description here" /></a></p>
  <p>And for Keycloak:
  <a href="https://i.stack.imgur.com/jiV5t.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/jiV5t.png" alt="enter image description here" /></a></p>
  <p>curl response:
  <a href="https://i.stack.imgur.com/u6Urs.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/u6Urs.png" alt="enter image description here" /></a></p>
  <p>I played around with decoding it on jwt.io, and it's saying the signature isn't verified, the public key doesn't seem to make it valid.</p>