I've been trying to figure this out for days and getting really crazy.
I have two microservices in spring cloud, one working as a gateway with zuul, running at localhost:8090, and the other runs at localhost:8002. This one hosts an endpoint which runs good if I hit directly to this microservice:
However, if I try to perform the same against zuul gateway (this microservice also works as authorization server) the result is:
Files of interest in zuul gateway microservice:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.contractar</groupId>
<artifactId>microservicio-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>microservicio-gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>19</java.version>
<maven.compiler.release>19</maven.compiler.release>
<spring-cloud.version>2022.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.contractar.microserviciogateway.MicroservicioGatewayApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
</plugin>
</plugins>
</build>
</project>
Main class:
package com.contractar.microserviciogateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableZuulProxy
public class MicroservicioGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(MicroservicioGatewayApplication.class, args);
}
}
application.properties:
spring.application.name=microservicio-gateway
server.port=8090
app.security.jwt.keystore-location=keys/keystore.jks
app.security.jwt.keystore-password=contractar
app.security.jwt.key-alias=keyAlias
app.security.jwt.private-key-passphrase=contractar
zuul.ignored-services='*'
zuul.routes.usuarios.service-id=microservicio-usuario
zuul.routes.microservicio-usuario.path=/usuarios/**
zuul.routes.microservicio-usuario.url=http://localhost:8002
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 90000
ribbon.ConnectTimeout: 9000
ribbon.ReadTimeout: 30000
ribbon.eureka.enabled=false
#MOVER DESPUES ESTO A AMBIENTE DE DEV
logging.level.org.springframework.security=TRACE
management.endpoints.web.exposure.include=*
Security config:
package com.contractar.microserviciogateway.security;
import java.util.Arrays;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig{
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(
new CorsFilter(corsConfigurationSource()));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Arrays.asList("*"));
corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "OPTIONS"));
corsConfig.setAllowCredentials(true);
corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin().usernameParameter("email");
http.headers().httpStrictTransportSecurity().disable();
http.cors().configurationSource(corsConfigurationSource()).and().csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeHttpRequests(
(requests) -> requests.requestMatchers(new AntPathRequestMatcher("/usuarios/**"), new AntPathRequestMatcher("/login"),
new AntPathRequestMatcher("/error"))
.permitAll().anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/error");
}
}
Finally, the target endpoint is defined as follows in it's microservice:
package com.contractar.microserviciousuario.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.contractar.microserviciousuario.models.Cliente;
import com.contractar.microserviciousuario.models.Proveedor;
import com.contractar.microserviciousuario.models.Usuario;
import com.contractar.microserviciousuario.services.UsuarioService;
import com.contractar.serviciocommons.proveedores.ProveedorType;
import jakarta.validation.Valid;
@RestController
public class UsuarioController {
@Autowired
private UsuarioService usuarioService;
@PostMapping("/usuarios")
public ResponseEntity<Usuario> crearUsuario(@RequestBody @Valid Usuario usuario) {
Usuario createdUsuario = usuarioService.create(usuario);
return new ResponseEntity<Usuario>(createdUsuario, HttpStatus.CREATED);
}
@PostMapping("/usuarios/proveedor")
public ResponseEntity<Proveedor> crearProveedor(@RequestBody @Valid Proveedor usuario,
@RequestParam(required = true) ProveedorType proveedorType) {
Proveedor createdUsuario = usuarioService.createProveedor(usuario, proveedorType);
return new ResponseEntity<Proveedor>(createdUsuario, HttpStatus.CREATED);
}
@PostMapping("/usuarios/cliente")
public ResponseEntity<Cliente> crearCliente(@RequestBody @Valid Cliente usuario) {
Cliente createdUsuario = usuarioService.createCliente(usuario);
return new ResponseEntity<Cliente>(createdUsuario, HttpStatus.CREATED);
}
@GetMapping("/usuarios")
public ResponseEntity<Usuario> findByEmail(@RequestParam(required = true) String email) {
Usuario usuario = usuarioService.findByEmail(email);
return new ResponseEntity<Usuario>(usuario, HttpStatus.OK);
}
}
I've checked zuul routing but didn't notice anything wrong. This was working well until I implemented the authorizarion layer.