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