nydiarra / springboot-jwt

Example Springboot Application for Securing a REST API with JSON Web Token (JWT). For an example Integration with Angular (version 2+) go to https://github.com/ipassynk/angular-springboot-jwt
548 stars 331 forks source link

CORS issues #5

Closed ctmay4 closed 7 years ago

ctmay4 commented 7 years ago

Thanks for this project. It was exactly what I needed. I do have one issue though. Everything works great through Postman. However the CORS headers are not showing up. Here are the headers returned from

GET http://localhost:9999/springjwt/cities

I had to change the port to 9999 but everything else is the same.

Cache-Control →no-cache, no-store, max-age=0, must-revalidate
Content-Type →application/json;charset=UTF-8
Date →Sun, 26 Nov 2017 14:07:37 GMT
Expires →0
Pragma →no-cache
Transfer-Encoding →chunked
X-Content-Type-Options →nosniff
X-Frame-Options →DENY
X-XSS-Protection →1; mode=block

None of the access control headers are there.

I tried to work around that by removing this class:

@Configuration
public class AdditionalWebConfig {
    /**
     * Allowing all origins, headers and methods here is only intended to keep this example simple.
     * This is not a default recommended configuration. Make adjustments as
     * necessary to your use case.
     */
    @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;
    }
}

and adding this new class:

@Component
public class SimpleCORSFilter extends OncePerRequestFilter {

    public SimpleCORSFilter() {
    }

    @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);
    }
}

If I make a successful call, that works great. All the access control headers are there.

Access-Control-Allow-Headers →authorization, content-type, xsrf-token
Access-Control-Allow-Methods →GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin →*
Access-Control-Expose-Headers →xsrf-token
Access-Control-Max-Age →3600
Cache-Control →no-cache, no-store, max-age=0, must-revalidate
Content-Type →application/json;charset=UTF-8
Date →Sun, 26 Nov 2017 14:14:29 GMT
Expires →0
Pragma →no-cache
Transfer-Encoding →chunked
X-Content-Type-Options →nosniff
X-Frame-Options →DENY
X-XSS-Protection →1; mode=block

However there is a still a problem. If I try to make an API call with an invalid or expired token, the access control headers are not there. The /oath/token call returns the headers even on bad login attempts. However the /api/** calls only return them when the token is correct. I've tried changing the @Order on the filter but it doesn't help. It seems like when the token is checked and is invalid the other filters never get executed.

Any thoughts on how to fix this? Thanks again.

ShanGor commented 7 years ago

have you set the security.oauth2.resource.filter-order=3? I met the problem before. I solved with below code.

@Bean public FilterRegistrationBean simpleCorsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.applyPermitDefaultValues(); config.setAllowCredentials(true); config.setAllowedOrigins(Arrays.asList("")); config.setAllowedHeaders(Arrays.asList("")); config.setAllowedMethods(Arrays.asList("*")); config.setExposedHeaders(Arrays.asList("content-length")); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; }

ctmay4 commented 7 years ago

Yes, I verified the following is in application.properties:

security.oauth2.resource.filter-order=3

I switched to your CORS filter implementation and still do not see any CORS headers on any call. The only way I've been able to see any CORS headers is to define a filter like in my original post (and that still has other issues like I mentioned).

ShanGor commented 7 years ago

um.. in fact the target is to remove this "X-XSS-Protection: 1; mode=block" When I checked out the source, I modified many things, and encountered the CORS issue. Then I tried many ways. but finally solved it with this simple snippet..

Not sure what's your root cause, good luck! maybe you can try to run the original package first, then add your code accordingly.

ctmay4 commented 7 years ago

Thanks for the help, but I checked out the project fresh to test again and when I call /oath/login and /springjwt/cities I get no CORS headers.

image

Then I changed AdditionalWebConfig to use the simpleCorsFilter in your post above. I still get no CORS headers.

@Configuration
public class AdditionalWebConfig {
    /**
     * Allowing all origins, headers and methods here is only intended to keep this example simple.
     * This is not a default recommended configuration. Make adjustments as
     * necessary to your use case.
     *
     */
    @Bean
    public FilterRegistrationBean simpleCorsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.applyPermitDefaultValues();
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Arrays.asList(""));
        config.setAllowedHeaders(Arrays.asList(""));
        config.setAllowedMethods(Arrays.asList("*"));
        config.setExposedHeaders(Arrays.asList("content-length"));
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

Any chance you also changed something else that fixed this for you?

ctmay4 commented 7 years ago

Ok, I think I have this working, but not like the way that was suggested. Here is how I fixed this for anyone that runs across the same issue:

  1. Removed the class AdditionalWebConfig.

  2. Add a new class called SimpleCORSFilter:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCORSFilter 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, Accept");
        response.addHeader("Access-Control-Expose-Headers", "WWW-Authenticate");
        if ("OPTIONS".equals(request.getMethod()))
            response.setStatus(HttpServletResponse.SC_OK);
        else
            filterChain.doFilter(request, response);
    }
}
  1. Finally, I'm not sure if this was necessary, but I updated the version of Spring Boot from 1.5.3 to 1.5.8.

This was basically what I was doing initially, but I guess I didn't set the @Order correctly on the filter:

@Order(Ordered.HIGHEST_PRECEDENCE)

I now get CORS headers on all my requests even when the key is invalid or missing.

ShanGor commented 7 years ago

The original version works fine without CORS issue. I think you can try to use a html to verify it, the problem is not the headers to contain "Access-Control-Allow-Origin".

The problem is this one "X-XSS-Protection: 1; mode=block", if this exist, then the browser will protect the resource, if you dont see this in the response header, then you can try to check it out in your HTML.

ctmay4 commented 7 years ago

I'm not sure why I could never get it to work, but my solution above removes all problems I had with CORS. The "X-XSS-Protection: 1; mode=block" is not causing me any issues. As I said above, when I checked out the project and ran it with no changes the CORS headers were not being returned. I will close this issue since I worked around the problem. Thanks for the help.