4

I am building an Angular 2 app with a Spring Boot backend. I am trying to fix a problem with CORS preflight for several days. According to this topic, it should work with CORS filter like this:

@Component
public class CorsFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else { 
            filterChain.doFilter(request, response);
        }
    }
}


@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends    ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
        .addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
        .headers()
            .frameOptions().disable()
            .and()
        .authorizeRequests()
            .antMatchers("/", "/home", "/register", "/login").permitAll()
            .antMatchers("/cottages").authenticated();
    }
}

Angular frontend:

  import {Injectable} from '@angular/core';
  import {Headers, Http} from "@angular/http";
  import {AppSettings} from "../app.settings";
  import { URLSearchParams } from '@angular/http'

  import {User} from "../_models/_index";
  import {Observable} from "rxjs";

  @Injectable()
  export class AuthenticationService {
   private headers = new Headers({'Content-Type': 'application/json'});
  private tokenHeaders = new Headers({
 'Content-Type': 'application/json',
 'client_id': 'xxx',
 'client_secret': 'xxx'});


constructor(private http: Http) {
}


login(user: User) {
let urlSearchParams = new URLSearchParams();
urlSearchParams.append('username', user.username);
urlSearchParams.append('password', user.password);
let body = urlSearchParams.toString();

return this.http.post(AppSettings.getApiUrl() + "oauth/token", body, { withCredentials: true, headers: this.tokenHeaders })
  .map((responseData) => {
    return responseData.json();
  })
  .map((item: any) => {
    return new User(item);
  })
  .catch((error: any) => Observable.of(error.json().error || 'Server error'));

 }
}

I tried other configuration which I have found on the this and other sources from the Spring docs.

I am always getting this error message:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/oauth/token. (Reason: CORS preflight channel did not succeed).

Simple CORS request to my own controller for example to register a user works perfectly.

Can anyone explain to me what I am doing wrong? Is there an error in my Java or Typescript code?

Edit:

The authorization server configuration:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends     AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("my-trusted-client").authorizedGrantTypes("client_credentials", "password")
            .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT").scopes("read", "write", "trust")
            .resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("xxx");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("isAuthenticated()");
     }
}
Arghavan
  • 1,125
  • 1
  • 11
  • 17
DBruns
  • 71
  • 1
  • 1
  • 6
  • did you add the path `/oauth/token` to your `permitAll()` section? – blurfus May 31 '17 at 23:43
  • thanks for your answer @ochi. I did not. I tried it this way in the ResourceServerConfiguration: `.authorizeRequests().antMatchers("/", "/oauth/token").permitAll().antMatchers("/cottages").authent‌​icated();` Unfortunately it do not fix the problem or get I something wrong? – DBruns Jun 01 '17 at 07:35

2 Answers2

3

Finally I have a solution for my problem. There was several errors on both sides (Angular/Java Spring Boot, Security). I will post my working code here and explain it. I will start with the Backend:

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {


@Override
public void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .antMatchers("/", "/users").permitAll().anyRequest()
    .authenticated()
    .and()
    .csrf().disable()

}
}

According spring.io tutorials an WebSecurityConfiguration is the better choice for my work - it will also work with the ResourceServerConfiguration. If I am honest I do not know what is the difference (when I have to use this and when the other).

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {

public SimpleCorsFilter() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) res;
    HttpServletRequest request = (HttpServletRequest) req;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, content-type");

    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
        response.setStatus(HttpServletResponse.SC_OK);
    } else {
        chain.doFilter(req, res);
    }
}

@Override
public void init(FilterConfig filterConfig) {
}

@Override
public void destroy() {
}


}

Without this CorsFilter I am only getting OPTIONS responses from the server.

I do not change the AuthorizationServerConfiguration which I have posted above.

Actually most errors were on the Angular / Frontend side. This works for me:

@Injectable()
export class AuthenticationService {

private headers = new Headers({'Content-Type': 'application/json'});

private auth64 = btoa("my-trusted-client:secret");
private tokenHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic '+this.auth64
});


constructor(private http: Http) {
 }


login(user: User) {
let body = new URLSearchParams();
body.append('grant_type', 'password');
body.append('username', user.username);
body.append('password', user.password);

return this.http.post(AppSettings.getApiUrl() + "oauth/token", body, {headers: this.tokenHeaders})
  .map(data => {
    console.log("it works!");
  }, error => {
    console.log(error.json());
  });

 }

The mapping before was a problem. It led always to the preflight CORS problem. I do not get the preflight error message with this mapping - even when I do not use a CORS filter - but you need the CORS filter to get something else than OPTIONS responses from the server.

Atfer this I got the following error message (in a JSON Response): "Full authentication is required to access this resource"

To fix the problem I did the following steps:

  • change the content-type to application/x-www-form-urlencoded (important for oauth2)
  • delete the headers client_id/client_secret
  • add the authorization headers
  • encode the values of my client_id/client_secrect with Base64(btoa)
  • add the encoded values to my authorization header

Maybe there are other/better ways to fix these problems but this code works fine for me - and maybe it helps someone else here :-)

DBruns
  • 71
  • 1
  • 1
  • 6
  • Wow, been hitting my head against the wall. Thanks you very much for such a detailed explanation. My issue was the same Pre-flight CORS not passing through. Setting the OPTIONS to return OK, did the trick. Thanks. – anz Mar 28 '19 at 03:58
0

You set the withCredentials to true in your request, but you missing Access-Control-Allow-Credentials in preflight response. See here about the header.

return ...withCredentials: true, headers: this.tokenHeaders })


response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");

response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
 // add this
response.setHeader("Access-Control-Allow-Headers", "true");

I noted that your add some other heads in your request, like client_id, but you didn't add them to the Access-Control-Allow-Headers.

chaoluo
  • 2,596
  • 1
  • 17
  • 29
  • thanks for your answer! Unfortunately this do not work for me. I added this line and the missing headers (client_id and client_secret) to allowed headers. I am still getting `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/oauth/token. (Reason: CORS preflight channel did not succeed).` – DBruns Jun 03 '17 at 22:05