2

I'm trying to make a POST to API endpoint which is secured with Spring Security. The aim of this POST is to pass credentials (username and password). I'm positive that securing is done correctly as I have tested it without POSTing from front-end and everything works fine.

My front-end is written in React, and I'm using fetch library for POSTing log-in credentials. The first fetch works correctly (it calls endpoint which is available to all users), but the second fails with message:

Fetch API cannot load http://localhost:8080/rest/book/anna/all. 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:8888' is therefore not allowed access. The response had HTTP status code 403. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

The issue is that I did enable CORS on that endpoint. So, my question is; what am I doing wrong so that I keep receiving error below along with 403? Firstly I receive 200 on GET, the 403 on OPTION, while I would expect 200 on POST.

My react class that is doing the fetch:

import React, {Component} from 'react';
import fetch from 'isomorphic-fetch';

class FAQ extends Component {
    componentDidMount() {
        const API_ENDPOINT = "http://localhost:8080/rest/book/all";
        fetch(API_ENDPOINT).then((response) => {
            console.log('response');
            console.log(response);
            console.log('after response');
            return response.json().then((json) => {
                console.log('json');
                console.log(json);
            })
        })
    }

    postLoginData(){
        fetch('http://localhost:8080/rest/book/anna/all', {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            credentials: 'include',
            method: 'POST',
            body: JSON.stringify({
                username: 'c',
                password: 'd',
            })
        }).then(response => console.log(response))
    }

    render() {
        return (
            <div>
                <p>Here some Frequently Asked Questions will be displayed.</p>
                {console.log('FAQ')}
                { this.props.children }
                <button type="button" onClick={this.postLoginData}>
                    post
                </button>
            </div>
        )
    }
}

export default FAQ;

SecurityConfig

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJpaRepositories(basePackageClasses = UsersRepository.class)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(getPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .authorizeRequests()
                .antMatchers("**/anna/**").authenticated()
                .anyRequest().permitAll()
                .and()
                .formLogin().permitAll();

        http
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/rest/author/all");
    }

    private PasswordEncoder getPasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return true;
            }
        };
    }
}

The called controller:

package com.shareabook.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@RestController
@RequestMapping("/rest/book")
public class BookController {
    @CrossOrigin
    @GetMapping(value = "/all")
    public String hello() {
        return "hello book controller all";
    }

    @CrossOrigin
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    @GetMapping(value = "/anna/all")
    public String securedHello() {
        return "hello secured book controller all";
    }
}

After changes suggested in comments I stopped receiving CORS error but I still have 403 error. I'm have no idea why since I'm sure that credentials are correct and while logging by hand on server it works perfectly.

My RequestHeader:

POST /login HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 31
Accept: application/json
Origin: http://localhost:8888
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json
Referer: http://localhost:8888/FAQ
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: JSESSIONID=9FA83D5C49B7351B8DA630B2617E49CA

ResponseHeader

HTTP/1.1 403
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Access-Control-Allow-Origin: http://localhost:8888
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 26 Sep 2017 17:34:23 GMT
werw werw
  • 221
  • 1
  • 3
  • 16
  • You should add a filter for your applications to treat the CORS. check here [example](https://stackoverflow.com/questions/37516755/spring-boot-rest-service-options-401-on-oauth-token/37517389#37517389) – Paulo Galdo Sandoval Sep 25 '17 at 04:46
  • @PauloGaldoSandoval why should I add a filter? Is @ CrossOrigin annotation not enough? I base my question on this post http://www.baeldung.com/spring-cors – werw werw Sep 25 '17 at 13:24
  • Seeing your previus message on that tutorial i saw there's a filter for CORS already, and the anotations i suggest you to try pass parameters to allow your origin – Paulo Galdo Sandoval Sep 25 '17 at 15:02
  • Also `@CrossOrigin` takes the method from `@RequestMapping` and you're using another type of mapping without the method – Paulo Galdo Sandoval Sep 25 '17 at 15:03
  • Possible duplicate of [CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource](https://stackoverflow.com/questions/42016126/cors-issue-no-access-control-allow-origin-header-is-present-on-the-requested) – dur Sep 25 '17 at 19:52
  • See also https://stackoverflow.com/questions/40418441/spring-security-cors-filter and https://stackoverflow.com/questions/38480394/spring-mvc-4-2-cors-returning-403 – dur Sep 25 '17 at 19:55
  • @dur and @PauloGaldoSandoval thank you for your help. I did menage to get rid of ` 'Access-Control-Allow-Origin' ` error. But I still have 403. I started wondering if I'm making a correct call. I want to log in. I don't have custom login endpoint, I'm using the one from spring security. So in React app I tried calling ('/login') and ('/rest/author/anna/all) but both times I receive 403. I enlosed updated JSON response. What should I do? Should I POST to another endpoint, create custom controller, change my JSON or maybe I can't use JSON or do something else? – werw werw Sep 26 '17 at 17:55
  • @dur I will take a look at them once again, but I believe that what solved the issue for me was PauloGaldoSandoval suggestion about adding origin and method. Yes it was due to CSRF. Once again many thanks for your help, it was most helpful! – werw werw Sep 26 '17 at 20:50
  • @werwwerw: You added origin and method to `@CrossOrigin`? That's strange, because the default is all origins and method should come from `RequestMapping`. – dur Sep 26 '17 at 21:03
  • @dur my annotations look like that: ` @CrossOrigin(origins = "http://localhost:8888") @PreAuthorize("hasAnyRole('ROLE_ADMIN')") @RequestMapping(value = "/anna/all", method = RequestMethod.POST)` – werw werw Sep 26 '17 at 21:44

0 Answers0