Spring security - 403 status response on OPTIONS call - spring

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.

Related

Permit only specific HTTP operations in Spring Security

I have a Spring security config with a standard (?) filter chain configuration to allow some open endpoints of my api:
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests() {
it.antMatchers("/auth/**").permitAll()
it.antMatchers("/info").permitAll()
}
.exceptionHandling().authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
return http.build()
}
This works fine. However I am currently trying to allow only GET operations on "/info". Is this somehow achievable in this config?
I tried permitAll() and #PreAuthorize("SOME_ROLE") in the Controller. This is results in an AccessDeniedException which is mapped to a HTTP 403 forbidden response which I do not want in this specific case. Not providing auth information to a protected API should result in a HTTP 401 response. I also could just define another path for the POST request, but this seems a bit clunky.
as M. Deinum posted in the comments the HTTPMethod can easily be specified in the antMatcher.
#Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests() {
it.antMatchers("/auth/**").permitAll()
it.antMatchers(HttpMethod.GET, "/info")
}
.exceptionHandling().authenticationEntryPoint(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
return http.build()
}

Spring Security: CORS blocks POST requests, but GET, PUT and DELETE are working

I have the following CORS configuration on my spring gateway:
#Configuration
#EnableWebFluxSecurity
public class GatewayConfig {
#Bean
public CorsWebFilter corsWebFilter() {
final CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Collections.singletonList("*"));
corsConfig.setMaxAge(3600L);
corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
corsConfig.addAllowedHeader("*");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}
It works perfectly fine with the GET, PUT and DELETE requests, but any POST request returns:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at <service-url>. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 302
Update:
Actually, for some reason it only blocks POST request on one route only.
This is the security configuration:
protected void configure(HttpSecurity http) throws Exception {
// Validate tokens through configured OpenID Provider
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
// Service security setup
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/polls").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/polls/*").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/polls/*").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/polls/{author:[\\s\\S]+}/vote").authenticated()
.antMatchers(HttpMethod.POST, "/polls/*").hasRole("ADMIN")
.anyRequest().permitAll();
}
CORS only blocks POST requests on the "/polls" route, while every other request works fine

403 error when requesting POST or PUT during Spring RestAPI development

I am trying to implement and use Rest API in a project to which Spring 5 + Spring Security 5 is applied.
When testing with Postman after running Tomcat locally
url: http://localhost:8080/api~
It has been confirmed that requests such as get, post, and put work normally.
I uploaded this project to https server and while testing the api on the server in local Postman,
A 403 forbidden error occurred in requests such as put and post, excluding get requests.
When I googled, it said that Spring Security's csrf problem + cors handling problem.
So I changed the code like that.
WebSecurityConfig
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/").permitAll()
~
.anyRequest().permitAll()
.and()
.formLogin()
~
.and()
.logout()
~
.and()
.httpBasic().disable().cors().configurationSource(corsConfigurationSource())
.and()
.sessionManagement()
~
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
WebMvcConfig
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedOrigins("*");
}
Controller
#CrossOrigin("*")
But in freflight request it returns 200 normally, but in this request I still get 403.
Which part is wrong? I would appreciate it if you let me know :)
In your configuration add add Cors filter via the HttpSecurity builder.
i.e.
Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.........
This will enable cors preflight requests which are used by PUT and POST.
you should not need to include a CorsConfigurationSource, just make sure you have the #CrossOrigin annotation in your Controllers as well.

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

Problems using Spring login in REST with CORS

I am trying to implement a website using REST. My strategy to authenticate the users consist of sending a JWT token to the user in reply to a username/password combination sent via POST. The most relevant part of my security conf is shown below.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/images/**", "/scripts/**", "/styles/**", "favicon.ico");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint()).and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.loginProcessingUrl("/login") //Not necesary because is the default
.permitAll().and()
.authorizeRequests()
.antMatchers("/api/getStatistics").permitAll()
.anyRequest().denyAll().and()
.addFilterBefore(new JwtTokenAuthenticationFilter(jWTTokenService()), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler(jWTTokenService());
}
#Bean
public JWTTokenService jWTTokenService() {
return new JWTTokenServiceImpl();
}
To allow the CORS access I have written the following lines in a class extending of WebMvcConfigurerAdapter
#Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept")
.allowedMethods("GET", "POST", "OPTIONS")
.allowCredentials(true).maxAge(3600);
registry.addMapping("/login")
.allowedOrigins("*")
.allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept")
.allowedMethods("POST", "OPTIONS")
.allowCredentials(true).maxAge(3600);
}
So when I make a call to /login sending the username and password it is supposed that Spring will catch the request, will process it and then will redirect to the success or failure handler.
Well, instead of that I have gotten an 403 Forbidden response during the CORS preflight. I decide to debug the program because I thought that when I wrote formLogin(), the UsernamePasswordAuthenticationFilter create a new AntPathRequestMatcher with the value ("/login", "POST").
What I found in the debug console was the following
Request 'OPTIONS /login' doesn't match 'POST /login
Of course it does not! Some hours later trying to solve the problem I discovered that everything works if I declare a empty method /login because during the preflight Spring finds the method and then send a 200OK to the client so the client then is allowed to send a POST that is captured by the UsernamePasswordAuthenticationFilter.
#Controller
public class LoginController {
#RequestMapping(value = { "/login" }, method = RequestMethod.POST)
public void dummyLogin() {
}
}
So, my question is: Should I really declare an empty method to "cheat" during the CORS preflight or it is just that I have missed something? Because it is not so elegant to declare a dummy method when you really want to delegate the job to the UsernamePasswordAuthenticationFilter...
The problem is that org.springframework.security.web.authentication.logout.LogoutFilter and org.springframework.security.web.authenticationUsernamePasswordAuthenticationFilter do not continue with the filter chain if they handled a login/logout. And since the configuration via WebSecurityConfigurerAdapter is processed later in the chain, the CorsProcessor is never applied.
I decided to keep the old solution and use a org.springframework.web.filter.CorsFilter.
It is not necessary to have empty method to make it work. The only thing you have to do is to allow OPTIONS call on the /login URL.
.antMatchers(HttpMethod.OPTIONS, "/login").permitAll()
Ex :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint()).and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.loginProcessingUrl("/login") //Not necesary because is the default
.permitAll().and()
.authorizeRequests()
.antMatchers("/api/getStatistics").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/login").permitAll()
.anyRequest().denyAll().and()
.addFilterBefore(new JwtTokenAuthenticationFilter(jWTTokenService()), UsernamePasswordAuthenticationFilter.class);
}

Resources