110

I am newbie in spring boot rest services. I have developed some rest api in spring boot using maven project.

I have successfully developed Get and Post Api. My GET Method working properly in postman and mobile. when i am trying to hit post method from postman its working properly but from mobile its gives 403 forbidden error.

This is my Configuration:

spring.datasource.url = jdbc:mysql://localhost/sampledb?useSSL=false
spring.datasource.username = te
spring.datasource.password = test
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

Please Suggest me how to solve error.

enter image description here

Harshal Deshmukh
  • 1,787
  • 3
  • 14
  • 25
  • Are you sending any header with postman? I think that you are sending a token in order to authenticate the request. Are you sending this token on POST from your mobile device? – desoss May 23 '18 at 10:44
  • 1
    Please add details of your spring-boot configuration. – Jannik Weichert May 23 '18 at 10:47
  • i am sending only Content type from postman and mobile @desoss – Harshal Deshmukh May 23 '18 at 10:49
  • please attach the logs, spring has surprisingly readable error messages in logs ;) – Jan Ossowski May 23 '18 at 10:53
  • This is my configurations: #spring.datasource.url = jdbc:mysql://192.168.4.2/maha?useSSL=false #spring.datasource.username = test #spring.datasource.password = test@123 # # ### Hibernate Properties ## The SQL dialect makes Hibernate generate better SQL for the chosen database #spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect # ## Hibernate ddl auto (create, create-drop, validate, update) #spring.jpa.hibernate.ddl-auto = update @JannikWeichert – Harshal Deshmukh May 23 '18 at 10:54
  • I'm not aware of your configuration, but if on your device you are sending the same headers, URI and HttpMethod of postman, it must work. (Uri is correct, right? :D) – desoss May 23 '18 at 10:55
  • Please edit your question und use correct formats. Otherwise it's not readable. Additionally please add your WebConfiguration. – Jannik Weichert May 23 '18 at 10:57
  • i have updated my question@JannikWeichert – Harshal Deshmukh May 23 '18 at 11:01
  • attach screenshot from postman and from the program you use on your mobile. There must be some difference. It is possible that requests don't work from outside localhost (dev security settings) but then `GET` wouldn't work either – Jan Ossowski May 23 '18 at 11:22
  • Get is working properly in mobile and postman – Harshal Deshmukh May 23 '18 at 11:25
  • I know, please attach the screenshots – Jan Ossowski May 23 '18 at 11:26

7 Answers7

250

you have to disable csrf Protection because it is enabled by default in spring security: here you can see code that allow cors origin.

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.cors().and().csrf().disable();
    }

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

}
Ezzat Eissa
  • 2,524
  • 1
  • 9
  • 3
  • 18
    disabling csrf made my POST requests successful! what a relief. – Pragalathan M Oct 06 '19 at 08:35
  • Thank you my friend! You saved my time! – AlexKh Apr 12 '20 at 19:15
  • I had the same situation combined with JWT based authentication. This solution worked for me one addition: I had to allow "OPTIONS" request to pass without JWT in Authorization header. – Marina982 Jul 08 '20 at 17:13
  • 4
    Why disabling csrf allowed post method? – sylikon Sep 10 '20 at 12:33
  • 6
    @Ezzat Eissa - Disabling `csrf` certainly works. But this also makes our APIs vulnerable, isn't it? If so how do make sure that csrf token is configured both from server and client side? – dev Mar 31 '21 at 10:28
  • 12
    This answer should come with some warning. There are some good reasons to keep csrf enabled. To work with csrf enabled: https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/html5/#csrf – Khamaseen Jun 21 '21 at 06:12
  • Mentioning comment from java doc : `Keep in mind however that theCORS spec does not allow "*" when allowCredentials is set to true and as of 5.3 that combinationis rejected in favor of using allowedOriginPatterns instead. ` – Ashish Patil Jun 24 '22 at 10:51
  • 1
    I spent days to just figure it out. There should be a better error message for what exactly is failing. Thanks for this! – maimoona Feb 24 '23 at 19:21
  • Spring 6 update, `http.csrf(AbstractHttpConfigurer::disable)` – Harold Castillo Jul 14 '23 at 08:52
11

In Spring Security Cross-site check is by default enable, we need to disable it by creating a separate class to stop cross-checking.

package com.baba.jaxws;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

     @Override
    //we have stopped the csrf to make post method work
        protected void configure(HttpSecurity http) throws Exception{
            http.cors().and().csrf().disable();
        }
}
Luis Paulo Pinto
  • 5,578
  • 4
  • 21
  • 35
Baba Fakruddin
  • 139
  • 1
  • 4
8

Possible causes:

  1. Requests done from postman are different to the one done from mobile (uri, method, headers)
  2. Invalid token
  3. CORS (read something about it, google is full of articles) add @CrossOrigin annotation to your controller.
  4. mobile app is doing an OPTION request before performing the POST, and you block OPTION requests. If also from postman the OPTION requests are blocked, add the property spring.mvc.dispatch-options-request=true. Moreover, in case you are using spring security, you have to explicitly allow OPTION requests also for it.
desoss
  • 572
  • 4
  • 26
7

CSRF is enabled by default in Spring Security. Having this enabled ensures a 403 error on HTTP requests that would change (object) states. For more information please visit: https://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/html5/#csrf

It is possible to disable CSRF in the Spring Security. However, it is enabled by default (convention over configuration) and for a good reason. This is also explained in the link provided to Spring's Security.

A working example, using Thymeleaf, might be:

HTML

<head>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</head>

JS

function postExample() {
    let token = $("meta[name='_csrf']").attr("content");
    let header = $("meta[name='_csrf_header']").attr("content");

    let data = {username: "", password: "", firstname: "", lastname: ""};

    // Object key string interpolation by {[header]:token} works with ES6
    fetch(window.location+"/addnote", {
        method:"POST",
        headers: {
            [header]: token,
            "charset": "UTF-8",
            "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
    }).then(res => console.log(res)).catch(err => console.log(err))
}

CONTROLLER per request of @mahmoud-magdy

@PostMapping("/addnote")
public Long addNote(@RequestBody() String data) {
    Gson gson = new Gson();
    JSONAddNote json = gson.fromJson(data, JSONAddNote.class);
    return <service>.addNote(json.username, json....);
}

class JSONAddNote {
    public String username;
    public String ...etc
}

Or a more direct CONTROLLER:

@PostMapping("/addnote")
public Long addNote(@RequestBody Data data) {
    return <service>.addNote(data);
}

class Data {
    public String username;
    public String ...etc
}
Khamaseen
  • 326
  • 3
  • 9
5

I was able to solve this by using:

<form th:action="@{url}" method="post">

Instead of:

<form action="url" method="post">

It seems the th:action tag does url rewriting to enable csrf validation.

Christopher
  • 411
  • 3
  • 7
  • 1
    Indeed indeed indeed! This is also explained [here](https://stackoverflow.com/a/51689803/6351897) : "_After some investigation I've found out that only forms that were using 'th:action' attribute (not plain 'action') had the csrf token injected._" – HelloWorld Mar 11 '22 at 13:51
  • This is the solution. I don't understand how disabling CSRF protection is the accepted answer. – Nikos Tzianas Aug 24 '22 at 11:54
2

To build on the accepted answer

Many HTTP client libraries (eg Axios) implicitly set a Content-Type: JSON header for POST requests. In my case, I forgot to allow that header causing only POSTS to fail.

@Bean
CorsConfigurationSource corsConfigurationSource() {
    ...
    configuration.addAllowedHeader("Content-Type"); // <- ALLOW THIS HEADER 
    ...
}
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
1

This answer is related to this question if you are deploying to Open/WAS Liberty server.

If so, you might get 403 error even though your code works perfectly fine if deploying to embedded Tomcat that comes with Spring boot.

Liberty does not read (or considers) your

server.servlet.context-path=/myapi/v1

that you set in your application.properties or application.yml file for some reason. Or, it just overwrites it, not sure. So, the above context-path will work just fine if deployment in Spring Boot embeded Tomcat container.

However, when you deploy it to OpenLiberty/WASLiberty, you might find that your endpoints will stop working and you get 403 and/or 404 errors.

In my example, I have api where I have /auth endpoint in my WebSecurityConfiguration class:

//Customize the /login url to overwrite the Spring default provided /login url.
private AuthenticationFilter authenticationFilter() throws Exception {
    final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
    // This works fine on embedded tomcat, but not in Liberty where it returns 403.  
    // To fix, in server.xml <appllication> block, add 
    // <application context-root="/myapi/v1" ... and then both
    // auth and other endpoints will work fine in Liberty.
    filter.setFilterProcessesUrl("/auth"); 
    // This is temporary "fix" that creates rather more issues, as it 
    // works fine with Tomcat but fails in Liberty and all other
    // endpoints still return 404
    //filter.setFilterProcessesUrl("/v1/auth"); 
    return filter;
}

Based on the above context-path, on Tomcat, it becomes /myapi/v1/auth while on Liberty, it ends up being just /myapi/auth which is wrong. I think what Liberty does, it will just take the name of the api and add to it the endpoint, therefore ignoring the versioning.

As a result of this, AntPathRequestMatcher class matches() method will result in a non-matching /auth end point and you will get 403 error. And the other endpoints will result in 404 error.

SOLUTION

In your application.properties, leave

server.servlet.context-path=/myapi/v1

, this will be picked up by embedded Tomcat and your app will continue to work as expected.

In your server.xml configuration for Open/WAS Liberty, add matching context-root to the section like:

<application context-root="/myapi/v1" id="myapi" location="location\of\your\myapi-0.0.1.war" name="myapi" type="war">

, this will be picked up by Open/WASLiberty and your app will continue to work as expected on Liberty container as well.

pixel
  • 9,653
  • 16
  • 82
  • 149