Search code examples
spring-bootspring-restcontroller

Spring Boot RestController DELETE request fails without .csrf().disable()


I have a Spring Boot Rest Service proof of concept.

I have this for my security: (obviously a poor real implmentation).

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 MySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        /* not production grade quality */
        httpSecurity.authorizeRequests().anyRequest().permitAll();
    }

//    @Override
//    public void configure(WebSecurity web) throws Exception {
//        web.debug(true);
//    }

}

Using Postman: All of my GETs were working fine. Then I added a DELETE request. and got

{
    "timestamp": "blah blah blah",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/v1/mything/1"
}

Postman setup: (not rocket science)

DELETE
http://localhost:8080/v1/mythings/1

So I added the ".csrf().disable()", and my DELETE works.

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 MySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        /* not production grade quality */
        httpSecurity.csrf().disable(); /* had to add this "Cross Site Request Forgery" disable for DELETE operations */
        httpSecurity.authorizeRequests().anyRequest().permitAll();
    }

//    @Override
//    public void configure(WebSecurity web) throws Exception {
//        web.debug(true);
//    }

}

But my question is WHY does .csrf().disable() .. allow DELETE requests? Seems somewhat unrelated.

Thanks.

My full rest controller below:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.inject.Inject;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;

@RestController
@RequestMapping("/v1")
public class MyThingController {

    private final Logger logger;
    private final IMyThingManager mythingManager;

    /* The Inject annotation is the signal for which constructor to use for IoC when there are multiple constructors.  Not needed in single constructor scenarios */
    @Inject
    public MyThingController(IMyThingManager mythingManager) {
        this(LoggerFactory.getLogger(MyThingController.class), mythingManager);
    }

    public MyThingController(Logger lgr, IMyThingManager mythingManager) {
        if (null == lgr) {
            throw new IllegalArgumentException("Logger is null");
        }

        if (null == mythingManager) {
            throw new IllegalArgumentException("IMyThingManager is null");
        }

        this.logger = lgr;
        this.mythingManager = mythingManager;
    }

    @RequestMapping(value = "/mythings", method = RequestMethod.GET)
    Collection<MyThingDto> getAllMyThings() {
        Collection<MyThingDto> returnItems = this.mythingManager.getAll();
        return returnItems;
    }


    @RequestMapping(method = RequestMethod.GET, value = "mythings/{mythingKey}")
    ResponseEntity<MyThingDto> getMyThingById(@PathVariable Long mythingKey) {

        this.logger.info(String.format("Method getMyThingById called. (mythingKey=\"%1s\")", mythingKey));

        Optional<MyThingDto> foundItem = this.mythingManager.getSingle(mythingKey);
        ResponseEntity<MyThingDto> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);

        if (foundItem.isPresent()) {
            responseEntity = new ResponseEntity<>(foundItem.get(), HttpStatus.OK);
        }

        return responseEntity;
    }




    @RequestMapping(value = "mythings/{mythingKey}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Integer> deleteUser(@PathVariable("mythingKey") Long mythingKey) {
        this.logger.info(String.format("Method deleteUser called. (mythingKey=\"%1s\")", mythingKey));

        int rowCount = this.mythingManager.deleteByKey(mythingKey);
        int rowCount = 1; /* use this to "fake it" */
        ResponseEntity<Integer> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);

        if (rowCount > 0) {
            responseEntity = new ResponseEntity<>(rowCount, HttpStatus.OK);
        }

        return responseEntity;
    }


}

Solution

  • CSRF protection checks for a CSRF token on changing methods like POST, PUT, DELETE. And as a REST API is stateless you don't have a token in a cookie. That's why you have to disable it for REST APIs.

    References