10
votes

I am using spring mvc to set up a rest api and most of the configurations are set up automatically through the spring boot project. On the front end I am using angularjs and their $http module to make ajax requests to the server for resources. Resource urls are defined in my controller class but only the GET urls are being matched. I've tried PUT and POST but these return 405 method not allowed and 403 forbidden respectively.

My controller looks like this

@Controller
@RequestMapping("/api/users")
public class UserController {

    @Inject
    UserService svc;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List<User> home() {
        return svc.findAll();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    @ResponseBody
    public User findById(@PathVariable long id){
        return svc.findById(id);
    }

    @RequestMapping(method = RequestMethod.PUT, value="/{id}")
    @ResponseBody
    public User updateUser(@PathVariable long id, @RequestBody User user){
        Assert.isTrue(user.getId().equals(id), "User Id must match Url Id");
        return svc.updateUser(id, user);
    }

}

and the request to the server that is not matching the url looks like this

$http({
            url: BASE_API + 'users/' + user.id,
            method: 'PUT',
            data:user
        })

this produces a PUT request to localhost:8080/api/users/1 and the server responds with a 405 Method Not Allowed response code.

The same request mapping but with a RequestMethod.GET is correctly handled when the server receives an HTTP GET request to localhost:8080/api/users/1

Any insight would really help.

PS in case this is needed the included spring boot dependencies are

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>

Thanks

6

6 Answers

7
votes

Have a look at the logs (with security at DEBUG) and you will discover the problem. Most likely you haven't disabled csrf protection and you client isn't sending the csrf token. (Standard Spring Security, not Spring Boot, although if you are using the Boot security autoconfig you can switch off csrf with an external config setting.)

12
votes

I had same error because of csrf protection. If csrf protection is enabled you need to send csrf parameters in request header.

You can also check Spring documentation here.

I added this parameters into my jsp file

<input type="hidden" id="csrfToken" value="${_csrf.token}"/>
<input type="hidden" id="csrfHeader" value="${_csrf.headerName}"/>

And modified my ajax call like below.

var token = $('#csrfToken').val();
var header = $('#csrfHeader').val();

$.ajax({
    type : 'POST',
    url : contextPath + "/qd/translate",
    data: JSON.stringify(json),
    dataType : 'json',
    beforeSend: function(xhr) {
        xhr.setRequestHeader("Accept", "application/json");
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.setRequestHeader(header, token);
    },
    success : function(result) {
        if (result.status === 'ok') {
            $('#translationModal').modal('hide');
            alert('Error when translating: ' + result.resultMessages.succeess);
        } else {
            alert('Error when translating: ' + result.resultMessages.error);
        }

    },
    error : function(jqXHR, textStatus, errorThrown) {
        alert(jqXHR.status + " " + jqXHR.responseText);
    }
});
2
votes

First of all, in Spring 4 you can use @RestController annotation on your rest controller class, which allows you to remove @RequestBody annotations from your methods. Makes it cleaner.

For second, would you maybe show more of your angular code? I think you should consider using factory services to make requests to backend. For example, have a service (this service already provides post, get, delete by default):

app.factory('User', function ($resource) {
    return $resource("/api/users/:id", {
        update: {
            method: "PUT"
        }
    });
});

Now in your Angular controller you make a new User object that you use to call this service methods.

Query all (GET): User.query();

Query one (GET): User.get({id: userId});

Insert (POST): User.$save();

Update (PUT): User.$update();

Remove (DELETE): User.delete({id: userId}

I've written my Angular apps like that also using Spring boot and works well.

2
votes

I had the same problem and resolved it by using the @RestController annotation instead of the "plain" @Controller annotation on my controller.

0
votes

Your code on Spring MVC looks good to me, so I suspect the syntax on AngularJS being correct. I am not familiar with AngularJS, but I found an online tutorial regarding the $http function

$http.get(url, config)
$http.post(url, data, config)
$http.put(url, data, config)
$http.delete(url, config)
$http.head(url, config)

from this link, http://tutorials.jenkov.com/angularjs/ajax.html

Hope it will help.

-4
votes

Your problem is that the endpoints for findById and updateUser are the same which is api/users/{id} and since the one with GET method comes first it will be evaluated first when you send a PUT request and hence will be rejected by spring.

I suggest you change the endpoint for updating user to api/users/update/{id} as below:

@Controller
@RequestMapping("/api/users")
public class UserController {

    @Inject
    UserService svc;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List<User> home() {
        return svc.findAll();
    }

    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    @ResponseBody
    public User findById(@PathVariable long id){
        return svc.findById(id);
    }

    @RequestMapping(method = RequestMethod.PUT, value="/update/{id}")
    @ResponseBody
    public User updateUser(@PathVariable long id, @RequestBody User user){
        Assert.isTrue(user.getId().equals(id), "User Id must match Url Id");
        return svc.updateUser(id, user);
    }

}