2

I can not access secured resource from another Origin. Searched a few days for solution and didn't find it, so I posted question here.

This is the story:

I created first Spring Boot Application that runs on default port 8080.

It depends on spring-boot-starter-data-rest and other dependencies and it has a GreetingRepository:

public interface GreetingRepository extends JpaRepository<Greeting, Long>, JpaSpecificationExecutor<Greeting> {}

globally enables CORS with RepositoryRestConfigurerAdapter:

@Configuration 
public class GlobalRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {

@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.getCorsRegistry()
            .addMapping("/**")
            .allowedOrigins("*")
            .allowedHeaders("*")
            .allowedMethods("*");
}
}

I created second Spring Boot Application that runs on port 9000 that will access this Greetings resource.

And it works. Second application sends HTTP request with method GET to http://localhost:8080/api/greetings and it gets response with Json data, with HEADER Access-Control-Allow-Origin: *. Everything is fine.

But.

Then I wanted to secure my resource in first application. There I included spring-boot-starter-security dependency and made configuration in WebSecurityConfigurerAdapter:

@EnableWebSecurity 
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService myUserDetailsService;

@Autowired
PasswordEncoder myPasswordEncoder;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(myUserDetailsService).passwordEncoder(myPasswordEncoder);
}

@Bean
public UserDetailsService createBeanUserDetailService() {
    return new MyUserDetailsService();
}

@Bean
public PasswordEncoder createBeanPasswordEncoder() {
    return new BCryptPasswordEncoder();
}
}

and made UserDetailsService and so on. (Important: I tested security before adding CORS, so this security configuration works and that is not a problem).

Then, after adding security in first application, second application sends same HTTP request with method GET to http://localhost:8080/api/greetings as the first time.

Now it gets an error: Failed to load http://localhost:8080/api/greetings: Redirect from 'http://localhost:8080/api/greetings' to 'http://localhost:8080/login' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access.

I can not find solution for this problem. So CORS works for Spring Repository resources, and Spring Security works, but I can not access secured resource from another Origin because of /login page. How to solve this?

st.
  • 87
  • 4
  • 1
    You are not logged in, so you are redirected to your login page. – dur Mar 28 '18 at 13:02
  • Do you really want to see the login page, if your REST call is not authenticated? – dur Mar 28 '18 at 13:07
  • @dur I know why I am redirected.I want to build API that can be accessible from another Origin (other websites and mobile apps). How can I authenticate request from another Origin using Ajax for example? I use Basic authentication. – st. Mar 28 '18 at 15:21
  • 1
    Your first problem is, that your request is not authenticated. Are you sure, that your request contains basic authentication? Are the username and password correct? Show the request with headers. After solving your first problem, you have also to solve the CORS problem. – dur Mar 28 '18 at 15:25
  • @dur My request is not authenticated. How to do that with Ajax? – st. Mar 28 '18 at 15:26
  • What framework do you use? Show your client code. – dur Mar 28 '18 at 15:30
  • $.ajax({ url: "http://localhost:8080/api/greetings" }).then(function(data) { // $('.greeting-id').append(data.id); $('.greeting-content').append(data._embedded.greetings[0].content); }) – st. Mar 28 '18 at 15:32
  • I'm no front-end developer. Use Google with "$.ajax" and "basic authentication". I'm sure, you will find a lot of answers. – dur Mar 28 '18 at 15:34
  • I used one with authentication: $.ajax({ url: "http://localhost:8080/api/greetings", beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", "Basic " + btoa(username + ":" + password)); }, }).then(function(data) { // $('.greeting-id').append(data.id); $('.greeting-content').append(data._embedded.greetings[0].content); }) But this I get in console: Failed to load http://localhost:8080/api/greetings: Response for preflight is invalid (redirect) – st. Mar 28 '18 at 15:36
  • 1
    Now you get to your CORS problem. See https://stackoverflow.com/a/42021652/5277820 – dur Mar 28 '18 at 19:09

1 Answers1

1

When Spring security is enabled, the security filters take precedence over CorsFilter. To make Spring Security aware of your CORS configuration call the cors() method in your HttpSecurity setup.

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
        .cors()
        .and()
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin().permitAll()
        .and()
        .httpBasic()
        .and()
        .csrf().disable();
}

This will give CorsFilter precedence over other Spring security filters. CorsFilter recognizes CORS preflight requests (HTTP OPTIONS calls) and allows it to go through without any security.

xsreality
  • 344
  • 4
  • 12