Search code examples
javaspring-bootdockerkeycloak

Error: connect ECONNREFUSED 8080 keycloak with spring boot


port 8080 is overlapped by port 8180

This is the first time I'm launching an application with keycloack, spring boot and docker. Setting up and working with keycloack itself did not cause any problems. Docker runs smoothly. But when I request from postman to spring methods, I get an error Error: connect ECONNREFUSED 127.0.0.1:8080

docker-compose.yml

version: '3.9'

services:
  keycloak:
    container_name: keycloak-auth
    image: quay.io/keycloak/keycloak:22.0.1

    command:
      - "start-dev"
    ports:
      - "8180:8080"
    networks:
      - keycloak
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: password
      KC_DB: postgres
      KC_DB_URL_HOST: keycloak-db
      KC_DB_URL_DATABASE: keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: password
      KC_HEALTH_ENABLED: true
    depends_on:
      - keycloak-db
  keycloak-db:
    image: postgres:14-alpine
    container_name: keycloak-db
    ports:
      - "5433:5432"
    volumes:
      - postgres_data_keycloak:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
    networks: [ keycloak ]
    healthcheck:
      test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U" ]
      timeout: 45s
      interval: 10s
      retries: 10

  activemq:
    image: webcenter/activemq:latest
    ports:
      # mqtt
      - "1883:1883"
      # amqp
      - "5672:5672"
      # ui
      - "8161:8161"
      # stomp
      - "61613:61613"
      # ws
      - "61614:61614"
      # jms
      - "61616:61616"
    networks: [ activemq ]
    volumes: [ "activemq-data:/opt/activemq/conf", "activemq-data:/data/activemq", "activemq-data:/var/log/activemq" ]
    environment:
      ACTIVEMQ_REMOVE_DEFAULT_ACCOUNT: "true"
      ACTIVEMQ_ADMIN_LOGIN: admin
      ACTIVEMQ_ADMIN_PASSWORD: password
      ACTIVEMQ_WRITE_LOGIN: write
      ACTIVEMQ_WRITE_PASSWORD: password
      ACTIVEMQ_READ_LOGIN: read
      ACTIVEMQ_READ_PASSWORD: password
      ACTIVEMQ_JMX_LOGIN: jmx
      ACTIVEMQ_JMX_PASSWORD: password

      ACTIVEMQ_STATIC_TOPICS: static-topic-1;static-topic-2;autoTopic
      ACTIVEMQ_STATIC_QUEUES: static-queue-1;static-queue-2
      ACTIVEMQ_ENABLED_SCHEDULER: "true"
      ACTIVEMQ_MIN_MEMORY: 512
      ACTIVEMQ_MAX_MEMORY: 2048

networks:
  keycloak:
    name: keycloak
    driver: bridge
  activemq: { }

volumes:
  postgres_data_keycloak:
    driver: local
  activemq-data:
    driver: local

securityConfig

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                //TODO: security without @PreAuthorize
                /* .authorizeHttpRequests(registry -> registry
                        .requestMatchers(HttpMethod.GET, "/api/**").hasRole("USER")
                        .requestMatchers(HttpMethod.POST, "/api/**").hasRole("PERSON")
                        .anyRequest().authenticated()
                )*/
                .oauth2ResourceServer(oauth2Configurer -> oauth2Configurer
                        .jwt(jwtConfigurer -> jwtConfigurer
                                .jwtAuthenticationConverter(jwt -> {
                                    Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access");
                                    Collection<String> roles = realmAccess.get("roles");

                                    var grantedAuthorities = roles.stream()
                                            .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                                            .collect(Collectors.toList());

                                    return new JwtAuthenticationToken(jwt, grantedAuthorities);
                                })));
        return httpSecurity.build();
    }
}

Controller

@RestController
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
@RequiredArgsConstructor
@Slf4j
public class AutomobileRestController implements AutomobileResource, AutomobileOpenApi, JMSPublisher {

    private final AutomobileRepository repository;
    private final JmsTemplate jmsTemplate;

    public static double getTiming(Instant start, Instant end) {
        return Duration.between(start, end).toMillis();
    }

    @Transactional
    @PostConstruct
    public void init() {
        repository.save(new Automobile(1L, "Ford", "Green", LocalDateTime.now(), LocalDateTime.now(), true, false));
    }

    @PostMapping("/automobiles")
    @ResponseStatus(HttpStatus.CREATED)
    @PreAuthorize("hasRole('ADMIN')")
    //@RolesAllowed("ADMIN")
    public Automobile saveAutomobile(@Valid @RequestBody Automobile automobile) {
        log.info("saveAutomobile() - start: automobile = {}", automobile);
        Automobile savedAutomobile = repository.save(automobile);
        log.info("saveAutomobile() - end: savedAutomobile = {}", savedAutomobile.getId());
        return savedAutomobile;
    }

    @GetMapping("/automobiles")
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("hasRole('USER')")
    public Collection<Automobile> getAllAutomobiles() {
        log.info("getAllAutomobiles() - start");
        Collection<Automobile> collection = repository.findAll();
        log.info("getAllAutomobiles() - end");
        return collection;
    }

    @GetMapping("/automobiles/{id}")
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("hasRole('PERSON')")
    public Automobile getAutomobileById(@PathVariable Long id) {
        log.info("getAutomobileById() - start: id = {}", id);
        Automobile receivedAutomobile = repository.findById(id)
                .orElseThrow(ThereIsNoSuchAutoException::new);
        if (receivedAutomobile.getDeleted()) {
            throw new AutoWasDeletedException();
        }
        log.info("getAutomobileById() - end: Automobile = {}", receivedAutomobile.getId());
        return receivedAutomobile;
    }

    @Hidden
    @GetMapping(value = "/automobiles", params = {"name"})
    @ResponseStatus(HttpStatus.OK)
    public Collection<Automobile> findAutomobileByName(@RequestParam(value = "name") String name) {
        log.info("findAutomobileByName() - start: name = {}", name);
        Collection<Automobile> collection = repository.findByName(name);
        log.info("findAutomobileByName() - end: collection = {}", collection);
        return collection;
    }

    @PutMapping("/automobiles/{id}")
    @ResponseStatus(HttpStatus.OK)
    //@CachePut(value = "automobile", key = "#id")
    public Automobile refreshAutomobile(@PathVariable Long id, @RequestBody Automobile automobile) {
        log.info("refreshAutomobile() - start: id = {}, automobile = {}", id, automobile);
        Automobile updatedAutomobile = repository.findById(id)
                .map(entity -> {
                    entity.checkColor(automobile);
                    entity.setName(automobile.getName());
                    entity.setColor(automobile.getColor());
                    entity.setUpdateDate(automobile.getUpdateDate());
                    if (entity.getDeleted()) {
                        throw new AutoWasDeletedException();
                    }
                    return repository.save(entity);
                })
                //.orElseThrow(() -> new EntityNotFoundException("Automobile not found with id = " + id));
                .orElseThrow(ThereIsNoSuchAutoException::new);
        log.info("refreshAutomobile() - end: updatedAutomobile = {}", updatedAutomobile);
        return updatedAutomobile;
    }

    @DeleteMapping("/automobiles/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @CacheEvict(value = "automobile", key = "#id")
    public String removeAutomobileById(@PathVariable Long id) {
        log.info("removeAutomobileById() - start: id = {}", id);
        Automobile deletedAutomobile = repository.findById(id)
                .orElseThrow(ThereIsNoSuchAutoException::new);
        deletedAutomobile.setDeleted(Boolean.TRUE);
        repository.save(deletedAutomobile);
        log.info("removeAutomobileById() - end: id = {}", id);
        return "Deleted";
    }

    @Hidden
    @DeleteMapping("/automobiles")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void removeAllAutomobiles() {
        log.info("removeAllAutomobiles() - start");
        repository.deleteAll();
        log.info("removeAllAutomobiles() - end");
    }

    @GetMapping(value = "/automobiles", params = {"color"})
    @ResponseStatus(HttpStatus.OK)
    public Collection<Automobile> findAutomobileByColor(
            @Parameter(description = "Name of the Automobile to be obtained. Cannot be empty.", required = true)
            @RequestParam(value = "color") String color) {
        Instant start = Instant.now();
        log.info("findAutomobileByColor() - start: time = {}", start);
        log.info("findAutomobileByColor() - start: color = {}", color);
        Collection<Automobile> collection = repository.findByColor(color);
        Instant end = Instant.now();
        log.info("findAutomobileByColor() - end: milliseconds = {}", getTiming(start, end));
        log.info("findAutomobileByColor() - end: collection = {}", collection);
        return collection;
    }

    @GetMapping(value = "/automobiles", params = {"name", "color"})
    @ResponseStatus(HttpStatus.OK)
    public Collection<Automobile> findAutomobileByNameAndColor(
            @Parameter(description = "Name of the Automobile to be obtained. Cannot be empty.", required = true)
            @RequestParam(value = "name") String name, @RequestParam(value = "color") String color) {
        log.info("findAutomobileByNameAndColor() - start: name = {}, color = {}", name, color);
        Collection<Automobile> collection = repository.findByNameAndColor(name, color);
        log.info("findAutomobileByNameAndColor() - end: collection = {}", collection);
        return collection;
    }

    @GetMapping(value = "/automobiles", params = {"colorStartsWith"})
    @ResponseStatus(HttpStatus.OK)
    public Collection<Automobile> findAutomobileByColorStartsWith(
            @RequestParam(value = "colorStartsWith") String colorStartsWith,
            @RequestParam(value = "page") int page,
            @RequestParam(value = "size") int size) {
        log.info("findAutomobileByColorStartsWith() - start: color = {}", colorStartsWith);
        Collection<Automobile> collection = repository
                .findByColorStartsWith(colorStartsWith, PageRequest.of(page, size, Sort.by("color")));
        log.info("findAutomobileByColorStartsWith() - end: collection = {}", collection);
        return collection;
    }

    @GetMapping("/automobiles-names")
    @ResponseStatus(HttpStatus.OK)
    public List<String> getAllAutomobilesByName() {
        log.info("getAllAutomobilesByName() - start");
        List<Automobile> collection = repository.findAll();
        List<String> collectionName = collection.stream()
                .map(Automobile::getName)
                .sorted()
                .collect(Collectors.toList());
        log.info("getAllAutomobilesByName() - end");
        return collectionName;
    }

    @Override
    @PostMapping("/message")
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<Automobile> pushMessage(@RequestBody Automobile automobile) {
        try {
            Topic autoTopic = Objects.requireNonNull(jmsTemplate
                    .getConnectionFactory()).createConnection().createSession().createTopic("AutoTopic");
            Automobile savedAutomobile = repository.save(automobile);
            log.info("\u001B[32m" + "Sending Automobile with id: " + savedAutomobile.getId() + "\u001B[0m");
            jmsTemplate.convertAndSend(autoTopic, savedAutomobile);
            return new ResponseEntity<>(savedAutomobile, HttpStatus.OK);
        } catch (Exception exception) {
            return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

Application.yml

spring:
  application:
    name: App
  profiles:
    active: development
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/automobiles
    username: postgres
    password: postgres
  jpa:
    hibernate:
      ddl-auto: update 
    show-sql: true
    database: postgresql
    database-platform: org.hibernate.dialect.PostgreSQLDialect
  security:
    oauth2:
      resource-server:
        jwt:
          issuer-uri: http://localhost:8180/realms/automobile-realm
  jms:
    pub-sub-domain: true
  activemq:
    broker-url: tcp://localhost:61616
server:
  port: 8080  
  servlet:
    context-path: /demo
logging:
  pattern:
    console: "%d %-5level %logger : %msg%n"
  level:
    org.springframework: info
    org.springframework.security: debug
    org.springframework.security.oauth2: debug
springdoc:
  swagger-ui:
    path: /swagger-ui.html 
  api-docs:
    path: /v3/api-docs.yaml
management:
  endpoints:
    web:
      exposure:
        include: "*"

When I make a request I get an error:

введите сюда описание изображения

In keycloak itself the following port settings введите сюда описание изображения

I checked that the port is not busy during the request, and keycloak works on port 8180. Maybe I missed some setting?

DockerFile

FROM openjdk:17
ADD /target/spring-boot-keycloak-docker-postgres.jar spring-boot-keycloak-docker-postgres.jar
ENTRYPOINT ["java", "-jar", "spring-boot-keycloak-docker-postgres.jar"]

docker-compose

enter image description here

logger docker-compose enter image description here


Solution

  • The problem is, your spring boot app is not getting started via your docker-compose file. This file is only starting up keycloak, it's DB and active mq services.

    You should package your spring boot app as a docker image (https://spring.io/guides/topicals/spring-boot-docker/) and use that as another service in your docker compose file.