Skip to content
Advertisement

No ‘Access-Control-Allow-Origin’ header is present (CORS) – Spring Boot (Spring security) Microservices + Vue.js

I’m working on Spring Boot project based on microservices architecture on backend and Vue.js on frontend. Structure of my project is next: Project structure

For avoiding CORS error usually I add @CrossOrigin annotation on to class and it works. It was all good and has been working well, until I added security part with ability to login users.

What did I did:

1. To API Gateway that built on spring-cloud-gateway I’ve added AuthFilter that uses as interceptor to create and check JWT:
api-gateway/src/main/java/.../AuthFilter.java

@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {
    private final WebClient.Builder webClientBuilder;

    @Autowired
    public AuthFilter(WebClient.Builder webClientBuilder) {
        super(Config.class);
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if(!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                throw new RuntimeException("Missing auth information");
            }

            String authHeader = exchange.getRequest().getHeaders().get(org.springframework.http.HttpHeaders.AUTHORIZATION).get(0);
            String[] parts = authHeader.split(" ");

            if(parts.length != 2 || !"Bearer".equals(parts[0])) {
                throw new RuntimeException("Incorrect auth structure");
            }

            return webClientBuilder.build()
                    .post()
                    .uri("http://manager-service/api/v1/auth/validateToken?token=" + parts[1])
                    .retrieve()
                    .bodyToMono(EmployeeDTO.class) //EmployeeDTO.class is custom DTO that represents User
                    .map(user -> {
                        exchange.getRequest()
                                .mutate()
                                .header("x-auth-user-id", user.getId());
                        return exchange;
                    }).flatMap(chain::filter);

        };
    }

    public static class Config {
        //live it empty because we dont need any particular configuration
    }
}

2. I’ve added AuthFilter as filter to each service in application.properties:
api-gateway/src/resource/application.properties

##Workshop service routes
spring.cloud.gateway.routes[0].id=workshop-service
spring.cloud.gateway.routes[0].uri=lb://workshop-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/v1/workshop/**
spring.cloud.gateway.routes[0].filters[0]=AuthFilter

##Manage service routes
spring.cloud.gateway.routes[1].id=manager-service
spring.cloud.gateway.routes[1].uri=lb://manager-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/v1/manage/**
spring.cloud.gateway.routes[1].filters[0]=AuthFilter

##Manage service for singIn. Here we dont need to add AuthFilter, cause sign in page should be available for all
spring.cloud.gateway.routes[2].id=manager-service-sign-in
spring.cloud.gateway.routes[2].uri=lb://manager-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/api/v1/auth/signIn

...

3. Manager-service microservice used to control base entities for system, such as users, roles, organizations where users working are and so on, so here I added SecurityConfig and WebConfig, because this microservice will be responsible for JWT generating:
manager-service/src/main/java/.../SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig  {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .authorizeRequests().anyRequest().permitAll();
        return httpSecurity.build();
    }
   }

manager-service/src/main/java/.../WebConfig.java

@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    private static final Long MAX_AGE=3600L;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders(
                        HttpHeaders.AUTHORIZATION,
                        HttpHeaders.CONTENT_TYPE,
                        HttpHeaders.ACCEPT)
                .allowedMethods(
                        HttpMethod.GET.name(),
                        HttpMethod.POST.name(),
                        HttpMethod.PUT.name(),
                        HttpMethod.DELETE.name())
                .maxAge(MAX_AGE)
                .allowedOrigins("http://localhost:8100")
                .allowCredentials(false);
    }
}

4. In controller, that represents auth I also added @CrossOrigin annotation to class:
manager-service/src/main/java/.../AuthController.java

@RestController
@RequestMapping("api/v1/auth")
@CrossOrigin(origins = "http://localhost:8100")
@Slf4j
public class AuthController {
    private final AuthService authService;

    @Autowired
    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    @PostMapping("/signIn")
    public ResponseEntity<EmployeeDTO> signIn(@RequestBody CredentialsDTO credentialsDTO) {
        log.info("Trying to login {}", credentialsDTO.getLogin());

        return ResponseEntity.ok(EmployeeMapper.convertToDTO(authService.signIn(credentialsDTO)));
    }

    @PostMapping("/validateToken")
    public ResponseEntity<EmployeeDTO> validateToken(@RequestParam String token) {
        log.info("Trying to validate token {}", token);
        Employee validatedTokenUser = authService.validateToken(token);
        return ResponseEntity.ok(EmployeeMapper.convertToDTO(validatedTokenUser));
    }
}

5. For frontend I use Vue.js. For requests I use axios. Here are post-request to login:

axios.post('http://localhost:8080/api/v1/auth/signIn', this.credentials).then(response => {
              console.log('response = ', response)
              console.log('token from response', response.data.token)
              this.$store.commit('saveToken', response.data.token)
            }).catch(error => {
          console.log('Error is below')
          console.log(error)
        })

All what I’m getting is an error: Access to XMLHttpRequest at 'http://localhost:8080/api/v1/auth/signIn' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.. Below you’ll see headers, that displays Chrome with request:
enter image description here
I’ve been trying to add another one corsConfiguration, tried to mark with CrossOrigin annotation only method, not class at all but it hadn’t take any effects. If I try to make such requests with postman it gives me expected response with generated token.

I’ll be grateful for any idea what could I do wrong.
Thanks!

UPDATE: As I understood well – all problems is in api-gateway. If I make requests directly to service – I get right response, but if I make request through gateway – I’m facing an error, logs of api-gateway below:

2022-07-05 00:34:18.128 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/workshop/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/manage/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/api/v1/auth/signIn" matches against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: manager-service-sign-in
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: OPTIONS http://localhost:8080/api/v1/auth/signIn] to Route{id='manager-service-sign-in', uri=lb://manager-service, order=0, predicate=Paths: [/api/v1/auth/signIn], match trailing slash: true, gatewayFilters=[], metadata={}}
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : [e5b87280-8] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@78df1cfc

Advertisement

Answer

After research I’ve solved problem. It was all Gateway’s fault
As I mentioned before, direct request gives me right response, but only if I go through api-gateway it gives me an errors.

So solution is to add CORS Configuration rules to gateway:

spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:8100"
            allowedHeaders: "*"
            allowedMethods: "*"

Please, note that if you don’t add section with gateway: default-filters you will be facing similar error with header that contains multiple values.

Thanks to answer by Pablo Aragonés in another question and Spring Cloud Documentation

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement