简体   繁体   中英

keycloak CORS filter spring boot

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.

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.

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.

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.

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 {...}

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 :)

I know the problem is too old but, I found better solution. Read more at official documentation

Inside your application.yml file

keycloak:
  auth-server-url: http://localhost:8180/auth
  realm: CollageERP
  resource: collage-erp-web
  public-client: true
  use-resource-role-mappings: true
  cors: true
  cors-max-age: 0
  principal-attribute: preferred_username
  cors-allowed-methods: POST, PUT, DELETE, GET
  cors-allowed-headers: X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers  

or you can config using application.properties file

  keycloak.auth-server-url= http://localhost:8180/auth
  keycloak.realm= CollageERP
  keycloak.resource= collage-erp-web
  keycloak.public-client= true
  keycloak.use-resource-role-mappings= true
  keycloak.cors= true
  keycloak.cors-max-age= 0
  keycloak.principal-attribute= preferred_username
  keycloak.cors-allowed-methods= POST, PUT, DELETE, GET
  keycloak.cors-allowed-headers= X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers  

and my java adaper class

import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

import javax.ws.rs.HttpMethod;

@KeycloakConfiguration
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and().authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .antMatchers("/api/**")
                .authenticated()
                .anyRequest().permitAll();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(keycloakAuthenticationProvider());
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

}

When your client is sending an Authentication header, you cannot use allowedOrigins("*"). You must configure a specific origin URL.

Since you have set the property keycloak.cors = true in your application.properties file, you have to mention the CORS enabled origins in the Keycloak server. To do that follow the below steps.

Go to Clients -> Select the client (Token owner) -> Settings -> Web Origins Add origins one by one or add * to allow all. After doing this you have to get a new token. (If you decode the token you will see your origins as allowed-origins": ["*"] )

Setting the property keycloak.cors = false is another option. But this completely disables CORS.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM