Spring Boot / React blocks CORS - reactjs

I've got an api configured with Spring Boot on my localhost.
My Spring Boot controller should allow CORS requests, as I'm working with #CrossOrigin:
#RestController
#CrossOrigin
#RequestMapping("/api")
public class imageController {
#GetMapping("/images")
public List<Image> findAll(){
return imageService.findAll();
}
}
When testing with Postman / cURL everything is working fine (but cURL doesn't care about CORS policies...).
Now I'm trying to access the ressource 'http://localhost:8081/api/images' from my React application with axios.
I get following response-header, which determines that the request was blocked because of CORS (see 'X-XSS-Protection').
Cache-Control
no-cache, no-store, max-age=0, must-revalidate
Connection
keep-alive
Date
Tue, 23 Mar 2021 11:10:54 GMT
Expires
0
Keep-Alive
timeout=60
Pragma
no-cache
Set-Cookie
JSESSIONID=0683F0AD7647F9F148C9C2D4CED8AFE6; Path=/; HttpOnly
Transfer-Encoding
chunked
Vary
Origin
Vary
Access-Control-Request-Method
Vary
Access-Control-Request-Headers
WWW-Authenticate
Bearer realm="Unknown"
X-Content-Type-Options
nosniff
X-Frame-Options
DENY
X-XSS-Protection
1; mode=block
My axios request looks like this:
function findAll() {
return instance.get('/api/images')
}
const instance = axios.create({
baseURL: `${config.API_BASE_URL}`,
headers: {
'Content-Type': 'application/json'
}
})
instance.interceptors.request.use(request => {
console.log('Starting Request', JSON.stringify(request, null, 2))
return request
})
instance.interceptors.response.use(response => {
console.log('Response:', JSON.stringify(response, null, 2))
return response
})
...and it's called by following code:
componentDidMount() {
this.setState({ isLoading: true })
ImageService.authToken(keycloak.token)
ImageService.findAll().then((res) => {
this.setState({ images: res.data, isLoading: false });
});
}
How do I configure Spring Boot to allow such requests and not blocking my request by CORS policies?
Note, that my application is secured by keycloak. But I dont think the configuration of keycloak is relevant for this case. Please let me know if you need the Spring Boot configuration of keycloak.

Hi you need to create a global cors configuration in your spring boot project.Create a class and annotate it with #Configuration. You can follow this example below.
#Configuration
public class MyConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
};
}
}
Here is the full guide that spring framework provides
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

Do you maybe have a Spring security configuration (#EnableWebSecurity / #EnableSpringSecurity) in place that may be interfering ? If so, you could configure cors() there as well.
#Configuration
#EnableWebSecurity
#EnableSpringSecurity
class WebSecurityConfiguration(...)
override fun configure(http: HttpSecurity) {
http.cors().configurationSource {
CorsConfiguration().apply {
allowedOrigins = listOf("*")
allowedMethods = listOf("GET", "OPTIONS")
allowedHeaders = listOf("*")
}
}
Also see https://docs.spring.io/spring-security/site/docs/4.2.19.BUILD-SNAPSHOT/reference/html/cors.html

Related

Autorization token from React doesn't pass Java Spring Boot filter

I'm building an app that has a backend in Java Spring boot, and a frontend in React. To pass a token from client to server I'm using a filter class that looks like this:
#Component
#Order(1)
public class ApplicationFilter implements Filter {
#Autowired
private UserDetails userDetails;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String token = httpRequest.getHeader("Authorization").substring(7);
Jws<Claims> jwsParsed = null;
try {
jwsParsed = Jwts.parserBuilder()
.setSigningKey("Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=".getBytes())
.build()
.parseClaimsJws(token);
} catch (Exception e) {
throw new AuthenticationException("The token is not correct.");
}
Long id = jwsParsed.getBody().get("id", Long.class);
String name = jwsParsed.getBody().get("name", String.class);
userDetails.setUserId(id);
userDetails.setUserName(name);
chain.doFilter(httpRequest, response);
}
In addition to this I'm configuring web config:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("PUT", "DELETE","POST","GET")
.allowedOrigins("*")
.exposedHeaders("*");
}
}
to let the requests pass the CORS policy. And also I've configured the context listener to use request context in the app:
#Configuration
public class RequestContextListenerConfig {
#Bean
#Order(0)
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}
Blockquote
On the client-side I'm defining an authentication service:
class AuthApiService {
api = null;
constructor() {
this.api = axios.create();
this.api
.interceptors
.request
.use(config => {
config.baseURL = "http://localhost:8080/";
config.headers = {
Authorization: `Bearer ${loginService.getUserToken()}`
};
return config;
});
}
getApi = () => this.api;
}
const authApiService = new AuthApiService();
export const authApi = authApiService.getApi();
export default authApiService;
and use it in my requests like this:
addItem(item) {
authApi.post("image", item)
.then((response) => {
item.id = response.data.id;
this.galleryItems.push(item);
this.dataWasChanged.next("ADD");
}).catch((err) => {
console.error(err.response);
});
}
The problem is when I'm running this add function it gives me an error in the console:
Access to XMLHttpRequest at 'http://localhost:8080/' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
and also in java:
ERROR 46280 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
Cannot invoke "String.substring(int)" because the return value of "javax.servlet.http.HttpServletRequest.getHeader(String)" is null
Never the less I'm sure that the token was sent because before an error I see it printed in the console. Besides, when I'm getting to the app through Postman, everything works with the token.
I printed header from Postman, and they are:
Header 'authorization' = Bearer eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbGdhIiwic3ViIjoiZ2FsbGVyeUFwcCIsImlkIjoyLCJuYW1lIjoiTWF4IiwiaWF0IjoxNjM1MTkwMDA5LCJleHAiOjE2Mzc4NzIwMDl9.KV7PNXDIITMQcmzsLOF0WMF34-t4PP47URMoiGJYvvs
Header 'content-type' = application/json
Header 'user-agent' = PostmanRuntime/7.28.4
Header 'accept' = */*
Header 'postman-token' = 21eee286-c554-442f-a169-ab82926165f7
Header 'host' = localhost:8080
Header 'accept-encoding' = gzip, deflate, br
Header 'connection' = keep-alive
Header 'content-length' = 85
but from the browser request I see these headers:
host: localhost:8080
connection: keep-alive
accept: */*
access-control-request-method: POST
access-control-request-headers: authorization,content-type
origin: http://localhost:3000
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
sec-fetch-mode: cors
sec-fetch-site: same-site
sec-fetch-dest: empty
referer: http://localhost:3000/
accept-encoding: gzip, deflate, br
accept-language: ru-RU,ru;q=0.9,en;q=0.8,he;q=0.7
No authorization header at all.
So the question is why the authorization token doesn't come to the java code from the react client? What am I doing wrong, where can be a bug? Or is there any other way to receive the token from the headers that I'm sending to the server?
It seems that preflight request is blocked by Spring Security filter.
You will need additional CORS configuration for Spring Security.
#Configuration
// unnecessary in most cases
// https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#features.developing-web-applications.spring-mvc.auto-configuration
// #EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("PUT", "DELETE","POST","GET")
.allowedOrigins("*")
.exposedHeaders("*");
}
}
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// if disabling CSRF protection
// https://docs.spring.io/spring-security/site/docs/5.5.3/reference/html5/#csrf-when
http.csrf().disable();
// if Spring MVC is on classpath and no CorsConfigurationSource is provided,
// Spring Security will use CORS configuration provided to Spring MVC
http.cors(Customizer.withDefaults());
}
}

Access-Control-Allow-Origin Problem in ReactJS

I have an issue when I try to access the backend.
Questions:
Do have any configure CORS in ReactJs?(in axios create or ..)
Are there any best way to do the CORS configuration in Spring Boot and ReactJs?
configurations are following below what I done,
Access to XMLHttpRequest at 'http://localhost:8282/opsprime/api/products?title_contains=a' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
So, I configured the backend(spring boot) as,
#RestController
#CrossOrigin(origins = "*", allowedHeaders = "*")
public class TestController {
....
}
and this is the reactJs configuration,
export default axios.create({
baseURL: 'http://localhost:8282/opsprime/api',
timeout: 1000,
headers: {
'Authorization' : `Bearer 4b21949a-4829-43cc-asex-1d0512478676`
}
});
Add this code to your spring boot application configuration, you can change the value of CORS_ORIGIN_ALLOW to identify on which methods CROSS can be implemented.
#Configuration
public class CorsSecurity implements WebMvcConfigurer {
private final String[] CORS_ORIGIN_ALLOW = {"/**"};
#Override
public void addCorsMappings(CorsRegistry registry) {
for (String cors : CORS_ORIGIN_ALLOW)
registry.addMapping(cors).allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
}
}

Keycloak secured Spring Boot Application CORS error when hitting endpoint using ReactJS application

I have a ReactJS and Java Spring Boot applications, both secured by Keycloak 11.0.2.
Keycloak is on port 8083, ReactJS on 3000 and Spring App is on 8085.
If I try to use the configuration provided below, I'm not able to hit my endpoint and I'm getting CORS error.
Firefox:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f52216b1-c235-4328-a2f9-d8448c3bf886&login=true&scope=openid. (Reason: CORS request did not succeed).
Chrome and Microsoft Edge:
Access to XMLHttpRequest at 'http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid' (redirected from 'http://localhost:8085/api/worker/create/product') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
xhr.js:184 GET http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid net::ERR_FAILED
When I try to hit my endpoint using Postman, I'm able to hit it. Below is my Keycloak Web Security configuration. The configuration uses application.properties file to configure Keycloak adapter.
When I set .authorizeRequests().antMatchers("/**").permitAll() in the config, I'm also able to hit my endpoint from browser and Postman.
#KeycloakConfiguration
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
final KeycloakAuthenticationProvider authProvider = keycloakAuthenticationProvider();
authProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
authBuilder.authenticationProvider(authProvider);
}
/**
* Call superclass configure method and set the Keycloak configuration
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.cors()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().anonymous()
//.and().authorizeRequests().antMatchers("/**").permitAll() //Uncomment for requests to be allowed!
.and().authorizeRequests().antMatchers("/api/admin/**").hasRole("ADMIN")
.and().authorizeRequests().antMatchers("/api/manager/**").hasAnyRole("MANAGER")
.and().authorizeRequests().antMatchers("/api/worker/**").hasRole("WORKER")
.anyRequest().authenticated();
}
/**
* Setup Auth Strategy. Don't add prefixes and suffixes to role strings
*/
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Don't use keycloak.json. Instead, use application.yml properties.
* #return
*/
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Here is a part of application.properties that sets up Keycloak:
spring:
jersey:
type: filter
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/token
jwk-set-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/certs
keycloak:
realm: sorcerer_realm
auth-server-url: http://localhost:8083/auth/
ssl-required: external
resource: event_sorcerer
verify-token-audience: true
credentials:
secret-jwt:
secret: d84611c9-af79-423b-b12c-bfa7fec23e85
use-resource-role-mappings: true
confidential-port: 0
Here is my ReactJS application's Keycloak adapter setup:
const keycloakConfig = {
"clientId": "event_sorcerer_frontend",
"realm": "sorcerer_realm",
"auth-server-url": "http://localhost:8083/auth/",
"url": "http://localhost:8083/auth",
"ssl-required": "external",
"resource": "event_sorcerer",
"public-client": true,
"verify-token-audience": true,
"use-resource-role-mappings": true,
"confidential-port": 0
};
const keycloak = new Keycloak(keycloakConfig);
const initKeycloak = (onSuccessCallback, onFailureCallback) => {
let success = false;
timeoutWrapper(() => {
if(!success){
onFailureCallback();
}
});
keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
}).then((isAuthenticated) => {
success = true;
if(isAuthenticated) {
onSuccessCallback();
} else {
login();
}
});
}
Here is how I send the request to server:
export const Request = {
configureAxiosDefault: () => {
axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
},
create: (data, endpoint, callback, errorCallback, finalCallback) => {
axios.post(serverEndpoint + endpoint, {
data: data,
headers: {
Authorization: `Bearer ${UserService.getToken()}`
}
})
.then(response => Utility.isEmpty(callback) ? defaultCallback(response) : callback(response))
.catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
.finally(response => {
if(!Utility.isEmpty(finalCallback)) {
finalCallback(response);
}
});
},
}
Here is my Keycloak configuration for frontend. Backend is the same, except the Access Type is confidential and the Root/Base url are different (not 3000 but 8085):
Here is my CORS configuration bean:
#Configuration
public class CORSConfiguration {
/**
* Setup CORS
* #return
*/
#Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
config.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
config.setAllowedHeaders(Arrays.asList(CorsConfiguration.ALL));
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return source;
}
}
And lastly, here is my endpoint. URL resolves to api/worker/create/product
#RestController
#RequestMapping(ControllerEndpointsPrefix.WORKER + "/create")
public class CreationController {
#Autowired
private UserAgregate userAgregate;
#PostMapping("/product")
public boolean createProduct(#RequestBody CreateProductCommand command) {
return true;
}
}
I've managed to solve this.
The problem wasn't on the server side, but on client side.
configureAxiosDefault: () => {
axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
axios.defaults.headers.Authorization = `Bearer ${UserService.getToken()}`
},
create: (data, endpoint, callback, errorCallback, finalCallback) => {
axios.post(serverEndpoint + endpoint, data)
.then(response => Utility.isEmpty(callback) ? defaultCallback(response) : callback(response))
.catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
.finally(response => {
if(!Utility.isEmpty(finalCallback)) {
finalCallback(response);
}
});
},
Server was unable to process the token, because I was sending it as a JSON object property. These changes made everything work OK.
So, CORS wasn't an issue at all. The issue was that request didn't contain an Authorization header.
There are a lot of StackOverflow questions regarding KeyCloak, and some of them incomplete and cryptic. I encountered a good amount of errors, because of OpenJDK, JDK versions etc.
If anyone needs explanations and solutions, working Spring Boot configuration is on my repository:
https://github.com/milosrs/EventSorcererBackend

POST oauth token sends initially OPTIONS request without data / headers and fails with 401

i have a problem with fetching oauth token from spring boot backend from react axios using :
async login() {
const tokenurl = 'http://localhost:8080/oauth/token';
const data = {
grant_type: 'password',
username: this.state.email,
password: this.state.password,
scope: 'write'
};
var headers = {
headers: {'Authorization' : 'Basic ' + btoa('client:secret') ,
'Content-Type': 'application/x-www-form-urlencoded'}
}
axios.post(tokenurl,data,headers)
a first OPTION request without headers / data is sent and 401 fails.
Access is denied (user is anonymous)
org.springframework.security.access.AccessDeniedException: Access is denied.
This is the request i received in my backend, the header / data are removed.
[OPTIONS /oauth/token HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Referer: http://localhost:3000/signin/
Origin: http://localhost:3000
Connection: keep-alive
Option request / post token are working successfully in curl as well as in Postman with get new access token.
curl -X POST -u "client:secret" -d "grant_type=password&username=tata#tata.com&password=test&scope=write" -H "origin:http://localhost:3000" -H "Access-Control-Request-Headers:authorization" http://localhost:8080/oauth/token
curl -X OPTIONS -u "client:secret" -d "grant_type=password&username=tata#tata.com&password=test&scope=write" -H "origin:http://localhost:3000" -H "Access-Control-Request-Headers:authorization" http://localhost:8080/oauth/token
I noticed with removing -u "client:secret" in the curl OPTIONS request leads to the the same error as axios.
Spring Boot Backend security and oauth2.0 configuration :
WebSecurityConfig :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource()).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic().realmName(securityRealm).and().csrf().disable();
}
#Bean(name="CorsSource")
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.applyPermitDefaultValues(); configuration.addAllowedMethod(HttpMethod.OPTIONS);
configuration.addAllowedOrigin("http://localhost:3000");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
RessourceConfig :
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(HttpMethod.POST,"/api/**").hasRole("PROVIDER").antMatchers(HttpMethod.GET, "/api/**").hasRole("CLIENT").antMatchers("/admin/**").hasRole("ADMIN");
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer.inMemory().withClient(clientId).secret(passwordEncoder.encode(clientSecret)).authorizedGrantTypes(grantType).scopes(scopeRead,scopeWrite).resourceIds(esourceIds); //ressourceIds:api,admin
}
}
Do you have any idea how can i fix this problem ?
thanks :)
after a lot of searching i could find a workaround to fix my problem in spring boot backend, in fact with enabling the authorizationServer, the framework will configure the permissions to access the oauth endpoints in the class AuthorizationServerEndpointsConfiguration and doesn't whitelist the OPTION method, see https://github.com/spring-projects/spring-security-oauth/issues/330
to fix the 401 problem :
1. in spring security config i added :
// ignore spring security preflight request for oauth (OPTIONS)
#Override
public void configure(WebSecurity web) throws Exception {
// TODO Auto-generated method stub
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/oauth/**");
}
and i added a cors filter, which will return OK by default if the method is OPTIONS.
(to avoid any conflict, i removed the use of the CorsConfigurationSource and the http.cors() in the ScurityConfig, see implementation in my question). The filter is ordered as HIGHEST_PRECEDENCE and it should be the first executed for each request.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
#WebFilter("/*")
public class CorsFilter implements Filter {
public CorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
final HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Max-Age", "3600");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
this workaround fixes my problem, but it's not a perfect solution, i will be very happy to get some other solutions.

Enable CORS in springboot mail API

I have easy mail sending springboot API and reactjs app for fetch.
Hosted in nginx server.
React app running well localhost, email working everything okei but if I run on a server it gives an error.
I have tried to add different Nginx server settings and add Springboot
#CrossOrigin(origins = "http://xx.xxx.xxx.xxx/") but have not found any help so far.
Where do I have to allow CORS if I deploy to server?
Nginx server default:
server {
listen 80 default_server;
root /var/www/name/build;
server_name xx.xxx.xxx.xxx;
index index.html index.htm;
location / {
}
}
Springboot:
#PostMapping
public void sendFeedback(#RequestBody Feedback feedback,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ValidationException("Feedback is not valid");
}
// Create a mail sender
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
Properties props = mailSender.getJavaMailProperties();
mailSender.setHost(this.emailCfg.getHost());
mailSender.setPort(this.emailCfg.getPort());
mailSender.setUsername(this.emailCfg.getUsername());
mailSender.setPassword(this.emailCfg.getPassword());
// Create an email instance
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(feedback.getEmail());
mailMessage.setTo("test#gmail.com");
mailMessage.setSubject("Testing mail");
mailMessage.setText(feedback.getFeedback());
mailSender.send(mailMessage);
}
Reactjs code I have:
fetch(url,{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: JSON.stringify(state)
}).then(response =>{
console.log(data)
}).catch(error =>{
console.log(error)
})
Output:
Access to fetch at 'http://xx.xxx.xxx.xxx:8080/feedback-0.0.1-SNAPSHOT' from
origin 'http://xx.xxx.xxx.xxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
Redirect is not allowed for a preflight request.
Add a CORS config as below :
CORSConfig.java
#Configuration
public class CORSConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD");
}
}
OR
Annonate your controller
#CrossOrigin
#PostMapping
public void sendFeedback(#RequestBody Feedback feedback,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ValidationException("Feedback is not valid");
}
// Create a mail sender
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
Properties props = mailSender.getJavaMailProperties();
mailSender.setHost(this.emailCfg.getHost());
mailSender.setPort(this.emailCfg.getPort());
mailSender.setUsername(this.emailCfg.getUsername());
mailSender.setPassword(this.emailCfg.getPassword());
// Create an email instance
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(feedback.getEmail());
mailMessage.setTo("test#gmail.com");
mailMessage.setSubject("Testing mail");
mailMessage.setText(feedback.getFeedback());
mailSender.send(mailMessage);
}
This is caused by your server not responding with the proper headers. If you look at your network tab you will see and OPTIONS request that fires before your POST request. Your server needs to respond to that OPTIONS request to let the browser know its ok for the requesting client to access the resource. You can read more about it here at MDN but at the very least your server should respond to the preflight with headers like:
Access-Control-Allow-Origin: http://foo.example <== your domain here
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
#CrossOrigin(origins="http://localhost:9000")
#GetMapping("/hello")
public Greeting greeting() {
return "world";
}
or
#CrossOrigin(origins="http://localhost:9000", maxAge=3600)
#RestController
public class RestController {}

Resources