What's the difference between #CrossOrigin annotation http.csrf().disable()? - spring-boot

This question is related to Disable Keycloak authentication for a specific url in spring-boot
I have a 3rd party dashboard which manages my front end through an iFrame. But it calls my search api directly through it's search widget. The code mentioned below does not solves the problem of CORS for this search API only and sends this error
, all the other API works smoothly.
#Override
protected void configure(#Nonnull final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
http
.cors()
.and()
.authorizeRequests()
.antMatchers("/health", "/error").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated();
}
Now I added the following code on my controller and it started working:
#CrossOrigin(origins = "https://dashboard-url")
so,
Is this the correct way to do this ? Is there any pitfalls using this ?
What is the difference between these 2, what is missing from my previous approach.
I have 3 stages(dev/stage/prod) onto which I might need to add this #CrossOrigin annotation, any suggestions how to proceed. I can make use of the profiles but prod does not have specific -prod tag e.g. dev and stage has the following url dashboard-dev.com/dashboard-stage.com. But prod has dashboard.com only.

Yes, what you are doing is correct
Please check the URL
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
https://docs.spring.io/spring-security/site/docs/5.1.3.RELEASE/reference/html5/#cors
If you are using Spring Security, make sure to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level.
So as per your coding, you have enabled cors at security using http.cors() and as there is no corsConfigurationSource (CORS filter ) been defined it uses the MVC level defined using #CORS(***)
However, if you want the values to be dynamic you can use as below with values fetching from external files based on env
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}

Related

Spring Security CORS Blocked By Policy

This should be easy, but of course since it's Spring Security, it's not.
I am attempting to access a relatively simple api running as a Spring Boot application from an Angular application. Angular makes the calls to the API just fine, but the backend blocks the request due to CORS policy:
I added the following to my Security configuration:
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://localtest.me:4200","http://localtest.me:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Bean
#Profile("dev")
public SecurityFilterChain devFilterChain(HttpSecurity http) throws Exception {
// define a custom filter, irrelevant to question
// #formatter:off
http
.addFilterAfter(filter, ConcurrentSessionFilter.class)
.authorizeRequests()
.antMatchers("/path1","/path2","/logout").permitAll()
.anyRequest().authenticated()
.and()
.cors();
// #formatter:on
return http.build();
}
This STILL does not prevent the CORS policy block.
I've also tried putting various iterations of #CrossOrigin (with and without origins argument):
on the Controller class
on the endpoint method itself
Am I making a simple error causing this?
Edit: I added breakpoints to Spring's CorsFilter, and they are not being hit.
Edit: Adding screenshot by request:
try to add this at the head ( beggining of your controller)
#CrossOrigin(origins = "http://localhost:{youy_angular_application_port}")
public class YourRestController {
}
Not the proudest and most beautiful solution, but some months ago, I also needed to expose some endpoints to my frontend, so my angular application could send requests to them.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/user").allowedOrigins("http://localhost:4200");
registry.addMapping("/post").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/").allowedOrigins("http://localhost:4200");
registry.addMapping("/user/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user/").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/user/").allowedOrigins("http://localhost:4200");
}
};
}
The bean can get implemented where ever, since its a bean. In my case I implemented it in the MainApplication.java class.
Okay, here's what happened.
At end-of-day the day before yesterday, some numbskull checked in a change to application.properties changing the context-root of the application.
The application was no longer being served at http://localtest.me:8000/api , it was being servered at http://localtest.me:8000/appname/api.
Effectively, I had a 404 error as much as I had a CORS error. Chrome didn't tell me that the path didn't exist, it just kept telling me it was blocked.

Resource server test fails with Auth0 issuer uri

I created a simple example, only acts Resource server to provide APIs for clients.
The complete codes can be found on Github - hantsy/spring-webmvc-auth0-sample.
I have browsed Spring security samples, it used a jwk-set-uri, in my application, I used issuer-uri instead.
security:
oauth2:
resourceserver:
jwt:
issuer-uri: <auth0 provided issuer uri>
And I followed Auth0 Spring security 5 API Guide , add audience claim validation.
I tried to add a ApplicationTests using MockMVC.
#Test
public void testGetById() throws Exception {
Post post = Post.builder().title("test").content("test content").build();
post.setId(1L);
given(this.posts.findById(anyLong())).willReturn(Optional.of(post));
this.mockMvc
.perform(
get("/posts/{id}", 1L)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("test"));
verify(this.posts, times(1)).findById(any(Long.class));
verifyNoMoreInteractions(this.posts);
}
And my security config is similar to this.
#Bean
SecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(c -> c
.antMatchers("/", "/info").permitAll()
.antMatchers(HttpMethod.GET, "/posts/**").permitAll()//.hasAuthority("SCOPE_read:posts")
.antMatchers(HttpMethod.POST, "/posts/**").hasAuthority("SCOPE_write:posts")
.antMatchers(HttpMethod.PUT, "/posts/**").hasAuthority("SCOPE_write:posts")
.antMatchers(HttpMethod.DELETE, "/posts/**").hasAuthority("SCOPE_delete:posts")
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.cors().and().build();
}
When running the tests.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.test.web.servlet.DefaultMvcResult.setHandler(Object)" because "mvcResult" is null
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
I am not sure where is wrong? I have checked the official samples, it includes a spring.factories file to enable a mockserver env, it is requried in the Spring Boot?
The issue appears to be the inclusion of cors() in the configuration.
Spring Security's CorsFilter delegates by default to HandlerMappingInterceptor, which wraps the request in a specialized HttpServletRequestWrapper. When combined with RouterFunctions, MockMvc's MVC_REQUEST_ATTRIBUTE request attribute is getting removed.
One fix, then, is to remove cors() from your configuration. Indeed, when I remove it from your sample, the tests run as expected.
Another is to not use the HandlerMappingInterceptor default. Instead, you can publish your own CorsConfigurationSource, like so:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
In your sample app, when I added the above to your SecurityConfig, the tests ran again as expected.
It seems like there might be a way to adjust this specialized wrapper in HandlerMappingIntrospector as well so as to not accidentally remove the MVC_REQUEST_ATTRIBUTE, but the Spring Framework team will probably have more to say if any adjustments are needed. I've filed a ticket there to see if anything can be done.

Spring boot custom response header blocked by CORS

I have Spring Boot application that provides GET REST API endpoint to a list of objects. The list has a default pagination and provides custom 'Link' HTTP header with info about the next and previous pages based on the current page in the navigation for the client to utilize.
Example of Link HTTP header
link: <http://localhost:8080/api/v1/articles?page=1&size=1>; rel="next", <http://localhost:8080/api/v1/articles?page=4&size=1>; rel="last"'
When the client and web server utilizes the same origin then the header is included. However, I am unable to include the link header in the response header when the client has different origin. The application has CORS configuration, but I couldn't find anything to make it include my custom header. Only default response headers are included.
Any idea how include custom HTTP headers in the CORS response?
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public SpringDataUserDetailsService customUserDetailsService() {
return new SpringDataUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin").authenticated().and().formLogin();
}
}
-- EDIT 1 --
Response headers when the client is of the same origin as web server
Response headers when the client is on the other port
I found the solution to my problem. The custom headers must be exposed in CorsConfigurationSource bean.
Adding this line of code, allowed cross orgin requests to get the custom header 'Link' in response.
configuration.addExposedHeader("Link");

Spring security - 403 status response on OPTIONS call

I have backend hosted on Heroku, and frontend on Netlify. When I call endpoint on backend it sends preflight OPTIONS but it gives 403 status.
I did search for solution but it still not working.
I want to be able to call "/authenticate" endpoint with "POST" method with body from FE to BE.
Spring security configuration (just configuration methods)
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
...
#Override
public void configure(WebSecurity web) throws Exception
{
web.ignoring()
.antMatchers(HttpMethod.POST, "/authenticate", "/register")
.antMatchers(HttpMethod.GET, "/token")
.antMatchers("/h2-console/**")
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/authenticate").permitAll()
.antMatchers(HttpMethod.GET, "/user-data").authenticated()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(<MY-URL>));
configuration.setAllowedHeaders(List.of("*"));
configuration.setMaxAge(Long.valueOf(3600));
configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
And call from FE
var req = new XMLHttpRequest();
req.open('POST', API_URL + '/authenticate', true);
req.setRequestHeader("Content-Type", "application/json");
req.withCredentials = true;
req.onreadystatechange = function (aEvt) {
if (req.readyState === 4) {
if(req.status === 200) {
console.log(req.responseText);
isAuthenticationSucessful = true;
}
else
console.log("Error loading site");
}
};
req.send(JSON.stringify({username, password}));
Browser dev-tools:
Reason: CORS header 'Access-Control-Allow-Origin' missing
Reason: CORS request did not succeed
TL;DR
Make sure that in setAllowedOrigins("https://myrul.com") you don't have trailing slash or you have exactly the same origin that your browser send.
If your endpoint is in web.ignoring(... delete it from here and put it in (with my example endpoint) http.authorizeRequests().antMatchers("/authenticate").permitAll()
(web and http according to my code in question)
Longer
So how I said in my comment, one thing that make it not working correctly was setting setAllowedOrigins("https://myrul.com/") in corsConfigurationSource.
Notice that trailing slash.
But I noticed in dev-tools that browser send origin header like this: Origin: https://myrul.com without trailing slash. To make it works I have to change allowed origins to proper origin like this: setAllowedOrigins("https://myrul.com") (without trailing slash).
This make browser able to send requests to server, and get 200 response, but browser don't accept response from server cuz CORS.
The next thing was that I have my endpoint in web.ignoring("/authenticate")... and according to this question
Spring Security Configuration - HttpSecurity vs WebSecurity
this statement prevents Spring Security Filter Chain where it should header Access-Control-Allow-Origin which tell browser that it can accept response. MDN Access-Control-Allow-Origin
So the answer for that was take my endpoint from web.ignoring("/authenticate") to http.authorizeRequests().antMatchers("/authenticate").permitAll().
But this makes another problem, that is it will go now to filter chain and to my custom filter http.addFilterBefore(new JwtFilter()..., so make sure to adopt custom filters to yours need.

iron-ajax request results in cors issue with spring boot

Currently I face a CORS issue when doing an ajax call (with the iron-ajax element of Polymer 2) to my server, developed using Spring Boot 2.
Performing a post request to /login via Postman returns in the expected results, however, using a browser like Safari or Chrome results in the following error:
Failed to load http://localhost:8080/login: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access. The response had HTTP status code 403.
My configuration in the back-end looks as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/users").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("admin")
.password("password")
.roles("ADMIN");
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
The ajax call is set up as follows:
<iron-ajax
id="postLoginAjax"
method="post"
headers='{"access-control-allow-origin": "*"}' // Tried with and without
content-type="application/json"
handle-as"json"
on-touch="touch"
on-response="handleUserResponse"
on-error"handleUserError">
</iron-ajax>
Based on other SO posts I have implemented the Bean, however still not successful.
EDIT:
Also following the global cors settings suggested at https://spring.io/blog/2015/06/08/cors-support-in-spring-framework does not result in what I want. I assume because it is relying on mvc dependency, which I am not using.
Thanks in advance for your help,
Chris

Resources