keycloak CORS filter spring boot

12,958

Solution 1

Try creating your CORS bean like my example. I recently went through the same thing (getting CORS to work) and it was a nightmare because the SpringBoot CORS support is currently not as robust or straightforward as the MVC CORS.

@Bean
public FilterRegistrationBean corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);

    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    bean.setOrder(0);
    return bean;
}

This is how I set it up to accept any origin application-wide, but if you change a few of the parameters you should be able to replicate what you want. ie. if you wanted to add only the methods you mentioned, chain some addAllowedMethod(). Allowed origins would be the same, and then your addMapping("/api/*") would become source.registerCorsConfiguration("/api/*", config);.

Edit:

Spring Data Rest and Cors

Take a look at this. Sebastian is on the Spring engineering team so this is about as good as you're going to get for an official answer.

Solution 2

I know.. the Problem is quite Old. But if you've Problems with the local development with Spring Boot + Keycloak you can use the Config

keycloak.cors=true

in your application.properties.

Cheers :)

Solution 3

Access-Control-Allow-Origin header is supposed to be set by the server application basis the Origin request header provided in the request to the server application. Usually browsers set the Origin header in request whenever they sense a cross origin request being made. And they expect a Access-Control-Allow-Origin header in response to allow it.

Now, for keycloak, I struggled with the same issue. Looking at this, it seems like keycloak does not add Access-Control-Allow-Origin header in case of error response. However, for me it was not adding this header in the response even in case of success response.

Looking into the code and adding breakpoints, I noticed that the webOrigin for client object was not getting populated from the Origin header even if passed and hence CORS was not adding the access control response header.

I was able to get it working by adding the following line of code just before the CORS build call:

client.addWebOrigin(headers.getRequestHeader("Origin").get(0));

before:

Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();

Once I built the code with this change and started the server, I started getting the three access control response headers:

Access-Control-Expose-Headers: Access-Control-Allow-Methods 
Access-Control-Allow-Origin: http://localhost:9000 
Access-Control-Allow-Credentials: true

I am using client credentials grant type; hence i added it only in the buildClientCredentialsGrant at TokenEndpoint.java#L473.

I still need to do some more code diving in order to say for sure that it is a bug for success responses at well and to find a better place to set this on the client object in keycloak code (like where client object is being constructed)

You are welcome to give it a try.

UPDATE:
I take this back. I re-registered my client in keycloak with Root URL as http://localhost:9000 (which is my front-end's application port) and i started getting the proper access control response headers. Hope this helps you.

Solution 4

I don't have access to code examples, but based on the code configurations you have included, it looks like a missing configuration is causing spring to exclude CORS headers.

J. West's response is similar to recent issues I encountered with Spring and CORS, I would however caution you to look into which implementation a spring example references, because there are two. Spring Security and Spring MVC Annotations. Both of these implementations work independent of each other, and can not be combined.

When using the filter based approach as you are (even boiled down), the key was to set allow credentials to true, in order for the authentication headers to be sent by the browser across domains. I would also advise using the full code method proposed above, as this will allow you to create a far more configurable web application for deployment across multiple domains or environments by property injection or a service registry.

Solution 5

I came here with the same problem and fix it ommiting authentication for OPTIONS method only, like this:

keycloak.securityConstraints[0].security-collections[0].omitted-methods[0]=OPTIONS

It worked for me because the OPTIONS request Keycloack does, does not include Authentication header.

UPDATE There was something with my browser's cache so I could not see the real impact of a change in my backend code. It looks like what really worked for me was enabling all CORS origins at @RestController level, like this:

@CrossOrigin(origins = "*")
@RestController
public class UsersApi {...}
Share:
12,958
krs8888
Author by

krs8888

Updated on June 24, 2022

Comments

  • krs8888
    krs8888 almost 2 years

    I am using keycloak to secure my rest service. I am refering to the tutorial given here. I created the rest and front end. Now when I add keycloak on the backend I get CORS error when my front end makes api call.

    Application.java file in spring boot looks like

    @SpringBootApplication
    public class Application 
    {
        public static void main( String[] args )
        {
            SpringApplication.run(Application.class, args);
        }
    
        @Bean
        public WebMvcConfigurer corsConfiguration() {
            return new WebMvcConfigurerAdapter() {
                @Override
                public void addCorsMappings(CorsRegistry registry) {
                    registry.addMapping("/api/*")
                            .allowedMethods(HttpMethod.GET.toString(), HttpMethod.POST.toString(),
                                    HttpMethod.PUT.toString(), HttpMethod.DELETE.toString(), HttpMethod.OPTIONS.toString())
                            .allowedOrigins("*");
                }
            };
        }
    } 
    

    The keycloak properties in the application.properties file look like

    keycloak.realm = demo
    keycloak.auth-server-url = http://localhost:8080/auth
    keycloak.ssl-required = external
    keycloak.resource = tutorial-backend
    keycloak.bearer-only = true
    keycloak.credentials.secret = 123123-1231231-123123-1231
    keycloak.cors = true
    keycloak.securityConstraints[0].securityCollections[0].name = spring secured api
    keycloak.securityConstraints[0].securityCollections[0].authRoles[0] = admin
    keycloak.securityConstraints[0].securityCollections[0].authRoles[1] = user
    keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /api/*
    

    The sample REST API that I am calling

    @RestController
    public class SampleController {    
        @RequestMapping(value ="/api/getSample",method=RequestMethod.GET)
        public string home() {
            return new string("demo");
        }        
    }
    

    the front end keycloak.json properties include

    {
      "realm": "demo",
      "auth-server-url": "http://localhost:8080/auth",
      "ssl-required": "external",
      "resource": "tutorial-frontend",
      "public-client": true
    }
    

    The CORS error that I get

    XMLHttpRequest cannot load http://localhost:8090/api/getSample. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access. The response had HTTP status code 401.
    
  • krs8888
    krs8888 over 7 years
    That does not work. I changed the allowedOrigins to the URL from from where my client is running. In my project it is running on localhost:9000. Even after changing that i still get the error
  • Trevor Bye
    Trevor Bye over 7 years
    Good point on the annotations. I edited my answer to include a link to a similar question that a Spring engineer was active on. If you had the same issue I'm guessing you came across it at some point.
  • krs8888
    krs8888 over 7 years
    sure i will let you know when i try it once I back from the holidays next week
  • krs8888
    krs8888 over 7 years
    changing my bean to what you have posted did not work. I think ill try using spring mvc and not use spring boot at all
  • Ava
    Ava about 3 years
    Worked for me! If you don't really care about CORS (for example building an app to learn something like me) that's the way to go. I've also disabled CORS using RepositoryRestConfigurer like here stackoverflow.com/a/60014831/9134945