spring-guides / gs-rest-service-cors

Enabling Cross Origin Requests for a RESTful Web Service :: Learn how to create a RESTful web service with Spring that support Cross-Origin Resource Sharing (CORS).

Home Page:https://spring.io/guides/gs/rest-service-cors/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

include spring security in example.

xenoterracide opened this issue · comments

I'm about 10 billion kinds of frustrated with the documentation right now. Official and especially this guide. So let's start with this guide. It doesn't include how to make it work with spring security. I know spring security isn't required for CORS, but I think it's probably at least an 80% of the time use case. This still doesn't give me proper CORS headers.

I honestly think this is beyond a problem with just documentation though... but that's a good place to start.

// © Copyright 2024 Caleb Cushing
// SPDX-License-Identifier: AGPL-3.0-or-later

package com.xenoterracide.controller.authn;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class ResourceServer {

  ResourceServer() {}

  public static void main(String[] args) {
    SpringApplication.run(ResourceServer.class, args);
  }

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
      .cors(Customizer.withDefaults())
      .httpBasic(c -> c.disable())
      .formLogin(f -> f.disable());
    return http.build();
  }

  @Bean
  HttpExchangeRepository httpExchangeRepository() {
    return new InMemoryHttpExchangeRepository();
  }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
    var cors = new CorsConfiguration();
    cors.addAllowedOrigin("http://localhost:3000");
    cors.addAllowedMethod("*");
    cors.addAllowedHeader("*");
    var source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", cors);
    return source;
  }

  @Bean
  WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/external").allowedOrigins("http://localhost:3000");
      }
    };
  }

  @RestController
  static class OidcTestController {

    private final Logger log = LogManager.getLogger(this.getClass());

    @CrossOrigin(originPatterns = "*")
    @GetMapping("/api/external")
    @NonNull
    String index(@Nullable Authentication details) {
      this.log.info("{}", details);
      var name = details != null ? details.getName() : "world";
      return "Hello, " + name;
    }
  }
}
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
> GET /api/external HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.6.0
> Accept: */*
> 
< HTTP/1.1 200 
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 0
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 12
< Date: Fri, 05 Apr 2024 10:13:21 GMT
< 
{ [12 bytes data]
* Connection #0 to host localhost left intact
Hello, world

I found out that a big part of my problem is "testing" apparently there's a hidden undocumented optimization that prevents the headers from being written if the expected headers aren't in the request. So it would be good in addition to showing the expected spring security config to include how to test it within a junit test. That would show that you need headers.