When I send a POST request to the server I get an error:
Failed to load http://localhost:8181/test: 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:4200' is therefore not allowed access. The response had HTTP status code 403.
The backend is written in Java Spring.
My method for creating a test:
createTest() {
const body = JSON.stringify({
'description': 'grtogjoritjhio',
'passingTime': 30,
'title': 'hoijyhoit'
});
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json'
}
)
};
return this._http.post(`${this._config.API_URLS.test}`, body, httpOptions)
.subscribe(res => {
console.log(res );
}, error => {
console.log(error);
});
}
Get Method works, but Post doesn't. They both work in Swagger and Postman. I changed POST method many times. The headers in my code do not work, but I solved the problem with them expanding to Google Chrome. There was only an error:
Response for preflight has invalid HTTP status code 403.
It seems to me that this is not Angular problem. Please tell me how I or my friend (who wrote the backend) can solve this problem.
PROBLEM :
For any Cross-Origin POST request, the browser will first try to do a OPTIONS call and if and only if that call is successful, it will do the real POST call. But in your case, the OPTIONS call fails because there is no 'Access-Control-Allow-Origin' response header. And hence the actual call will not be done.
SLOUTION :
So for this to work you need to add CORS Configuration on the server side to set the appropriate headers needed for the Cross-Origin request like :
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "content-type, if-none-match");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Max-Age", "3600");
You need to ensure that the Spring accept CORS requests
Also if you have applied Spring security & require authorization headers for example for your API, then (only if you need to make your app support CORS) you should exclude the OPTIONS calls from authorization in your spring security configuration file.
It will be something like this:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
#Override
public void configure(WebSecurity web) throws Exception {
// Allow OPTIONS calls to be accessed without authentication
web.ignoring()
.antMatchers(HttpMethod.OPTIONS,"/**")
Note:
In production, probably it is better to use reverse proxy (like nginx) & not allow the browsers to call your API directly, in that case, you don't need to allow the OPTIONS calls as shown above.
I've had the exact same problem with Angular lately. It happens because some requests are triggering preflight requests eg. PATCH, DELETE, OPTIONS etc. This is a security feature in web browsers. It works from Swagger and Postman simply because they don't implement such a feature. To enable CORS requests in Spring you have to create a bean that returns WebMvcConfigurer object. Don't forget of #Configuration in case you made an extra class for this.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
.allowedHeaders("*");
}
};
}
Of course, you can tune this up to your needs.
Related
I'm coding a api in spring boot and spring security that is accessed by a react front-end, the get methods are working good. But when it comes to posts where we have a options as preflight It is returning 401 http status. As I am developing, for now I just want that cors don't block anything. This error don't occur on Insomnia or postman, where I can do the requests without this error. The main endpoint that this error is occouring is /oauth/token, that is the deafault endpoint of spring boot to get the Bearer token, the post that I send there by the react app is given this error:
Access to XMLHttpRequest at 'http://localhost:8080/oauth/token' 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.
My spring configurations:
WebConfig
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT");
}
}
#EnableWebSecurity
#EnableAuthorizationServer
#EnableResourceServer
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable();
}
}
After doing some searches I think that is cors or some permission that I am not given, but other approaches will be consider.
AFAIK in order for CORS to be properly configured you also need to specify the allowed origins, headers and other stuff if applicable for your use case.
So your CORS mapping would become:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") //or whichever methods you want to allow
.allowedOrigins("*") //or www.example.com if you want to be more specific
.allowedHeaders("Content_Type", "Authorization"); //i also put Authorization since i saw you probably want to do so
}
Check this picture, maybe it makes you understand better how it works.
Intention:
Consume a REST API in Angular that is exposed via a SpringMVC based web application. Both are running in different hosts
Problem:
Although the API I am requesting is a GET Request, Angular behind-the-scenes first makes an OPTIONS request to the REST API SpringMVC server. This throws back a 500 server error (see CURL output below).
Tried hitting the same API using Postman tool (GET request), surprisingly its giving desired output (i.e. also gives Access-Control-Allow-Origin header) without any error, but OPTIONS request throws 500 server error.
Tech Stack I am using:
Angular 6 (runs atop NodeJS)
Spring MVC 4.3.6.RELEASE (with no Spring security explicitly configured) [Java config based Spring configuration]
Jetty-Runner 9.4.1 (to run the WAR file of Spring MVC webapp).
Error Message got by Angular:
Access to XMLHttpRequest at 'http://localhost:8080/v1/create' from origin
'http://localhost:4200' 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.
Code Snippets:
Angular code:
public createDomainObj() {
return this.http.post('http://localhost:8080/v1/create', request body parameter)
}
SpringMVC code:
#ResponseBody
#RequestMapping(value = "/v1/create", method = RequestMethod.POST)
#AccessController(accessLevel = "Anonymous")
public <APIResponseModelClass> anAPIMethod(#RequestBody param1, param2) {
//code logic
return <obj>;
}
What's tried already:
CORS Filter in SpringMVC, all combinations of Annotations, but no luck.
Have also tried suggestions mentioned in below links to no success:
https://www.baeldung.com/spring-security-cors-preflight
How to add Access-Control-Allow-Origin to jetty server
CURL is able to reproduce the problem:
REQUEST:
curl -H "Origin:*" -H "Access-Control-Request-Method: GET, POST, PUT, DELETE"
-H "Access-Control-Request-Headers: X-Requested-With"
-X OPTIONS --verbose http://localhost:8080/v1/create
RESPONSE:
Trying 127.0.0.1...
Connected to localhost (127.0.0.1) port 8080 (#0)
OPTIONS /v1/create HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.47.0
Accept: */*
Origin:*
Access-Control-Request-Method: GET, POST, PUT, DELETE
Access-Control-Request-Headers: X-Requested-With
Content-Length: 392
Content-Type: application/x-www-form-urlencoded
upload completely sent off: 392 out of 392 bytes
HTTP/1.1 500 Server Error
Connection: close
Server: Jetty(9.4.2.v20170220)
Closing connection 0
So, how to make Angular to consume the REST API from SpringMVC that has OPTIONS preflight aspect?
I can say about issue,
CORS:Response to preflight request doesn't pass access control,
There are two types of requests,
1) Simple
Have some criteria, simple exchange of cors headers, allowed methods, headers, content-types
2) preflight
Those doesnt match simple request criteria are preflight, for example,
we send a DELETE request to the server. The browser sends OPTIONS request with headers containing info about the DELETE request we made.
OPTIONS /users/:id
Access-Control-Request-Method: DELETE
simple thing to fix is you can remove or change any complex headers that aren't needed.
Header set Access-Control-Allow-Origin "*" setting this will work for simple CORS requests, so for more complex request having custom headers value wont work, thats the preflight mechanism of the browser it checks that service accepts request or not,
remeber that it includeds,
Access-Control-Request-Headers
Access-Control-Request-Method
Access-Control-Allow-Origin
it seems you need to add cors in http configure thats cors filter,
different ways enabling cors,
1) Controller method CORS configuration
#CrossOrigin(origins = "http://localhost:9000")
#GetMapping("/greeting")
public Greeting greeting(#RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
2) Global CORS configuration
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
3) Enabling webSecurity, try adding http.cors()
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.cors();
}
}
I have a backend server made in Java with Spring Boot, Security and Web and a client made with Angular.
Currently I am trying to make a simple request under localhost:8080/resource.
The controller for this address is shown bellow:
#RestController
public class IndexController {
#CrossOrigin
#RequestMapping("/resource")
public Map<String, Object> home() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
}
And the Angular client (the part that performs the request) is this:
import { Component } from "#angular/core";
import { HttpClient } from "#angular/common/http";
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
public title = "Security Client";
public greeting = {};
constructor(private http: HttpClient) {
http.get("http://localhost:8080/resource").subscribe(data => this.greeting = data);
}
}
The problem by using just what was shown is that I get a CORS error.
Whether removing Spring Security from my pom.xml or adding this configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/resource").permitAll();
}
}
Solves the problem.
What I wanna know is why I am getting an CORS error instead of a 401 Unauthorized when accessing an address that demands user authentication.
According to the spring boot documentation:
For security reasons, browsers prohibit AJAX calls to resources
outside the current origin. For example, you could have your bank
account in one tab and evil.com in another. Scripts from evil.com
should not be able to make AJAX requests to your bank API with your
credentials — for example withdrawing money from your account!
Cross-Origin Resource Sharing (CORS) is a W3C specification
implemented by most browsers that lets you specify what kind of
cross-domain requests are authorized, rather than using less secure
and less powerful workarounds based on IFRAME or JSONP.
You're getting this error because you need to add a filter in your security configuration. In your configure, add:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.authorizeRequests().antMatchers("/resource").permitAll();
}
In the same file, you should add:
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type",
"x-auth-token"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
This works fine for me.
What I wanna know is why I am getting an CORS error instead of a 401
Unauthorized when accessing an address that demands user
authentication.
You get this error because before your actual request (POST, GET...), the browser performs a pre-flight request (OPTIONS) to validate if in fact the called server is able to handle CORS requests.
During this request, the Access-Control-Request-Method and Access-Control-Request-Header are validated and some other info are added to the header.
You receive the CORS error then because your actual request is not even done if CORS validation failed on the OPTIONS request.
You can check a flowchart of how CORS validation works in here
An interesting point is that you will only get a HTTP error status like 401 during the pre-flight request when the server is not authorized to answer the OPTIONS request.
I am using Angular6 as FrontEnd running on http:// localhost:4200 and Spring Boot (which has in built Tomcat server) as backend (exposing a GET Rest API) running on https:// localhost:8083/ldap . When I run this, I am getting CORS policy error on the browser. So I searched on internet and tried multiple fixes which were suggested on internet. I am not sure what I am missing on each of the solution below.
Unsuccessful Fix 1: I tried to run it via proxy.
-> Created a proxy.config.json in parallel to package.json with below
content.
{
"/ldap/": {
"target": "https://localhost:8083",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
-> Added below entry in package.json inside script block there.
"start":"ng serve --proxy-config proxy.config.json",
-> In the service class, tried calling my spring boot backend rest API
like below.
return this.http.get('/ldap');
Now when I run my app, I got below error:
GET http:// localhost:4200/ldap 404 (Not Found) : zone.js:3243
Unsuccessful Fix 2: I added below headers before calling the Rest API in my frontend.
getUserAuthenticatedFromLDAP() {
const httpOptions = {
headers: new HttpHeaders({
'crossDomain': 'true',
'mode' : 'cors',
'allowCredentials': 'true',
'origins': '',
'allowedHeaders': '',
'Access-Control-Allow-Origin': '',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
'Access-Control-Max-Age': '86400'
})
};
return this.http.get('https://localhost:8083' , httpOptions);
}
Access to XMLHttpRequest at 'https://localhost:8083/' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Unsuccessful Fix 3: Rather than making changes at front end, I tried to make changes at API level by adding below code at controller level.
#Controller
#CrossOrigin(origins = "http://localhost:4200")
public class WelcomeController {
// This is for LDAP Authentication
#GetMapping("/ldap")
#ResponseBody
public Authentication hello() {
return LdapSecurity.getAuthentication();
}
}
Here I am getting below error again:
Access to XMLHttpRequest at 'https://localhost:8083/' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
More unsuccessful fixes:
I even tried to change headers of Tomcat using application.properties file but could not find sufficient example or piece of code to make any change.
On internet, some people suggested to implement filter on API level but I am not sure that in whcih class I need to add those overriden filter method. I am stuck in this issue for last 2 days.
PS: I see some people have implemented CORS filter at API layer or implemented class like below. Now my question is if I implement below class then where do I need to refer this class ? Is it web.xml or any other place.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
I was using Spring security feature for LDAP authentication. But now I removed Spring security feature for LDAP and used a basic program in java to make a connection with LDAP. After that I used CrossOrigin tag in controller layer and now I am not getting CORS issue. Thanks you all for your help.
please have a look here, you need to enable cors in your method/controller
#CrossOrigin(origins = "http://localhost:9000")
#GetMapping("/greeting")
public Greeting greeting(#RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
I am using jersey as my restful api implementation. In the front end, I am using angularjs $http service to make http request. When I request a delete method I always got below error.
"Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response."
I read some articles and they say I need to allow delete on "Access-Control-Allow-Methods". I have setup the response filter as below but it still has such problem. What else should I do?
#Provider
public class CORSResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Access-Control-Allow-Methods", "*");
}
}
below is my angular code to make the request:
$http({
method: 'DELETE',
url: remoteUrl,
headers : {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
'ACCESS_TOKEN' : $cookieStore.get("access_token")
},
data : $httpParamSerializer({
'id':id
})
}).success(function(data,status,headers,config) {
$scope.refreshDepartments();
console.log(data);
alert("success");
}).error(function(data,status,headers,config){
console.log(data);
alert("error");
});
After some testing, I found the solution. I put the allow method on the header as below, then it works. I don't know why "*" doesn't work.
headers.add("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
The value " * " only counts as a special wildcard value for requests without credentials (requests without HTTP cookies or HTTP authentication information). In requests with credentials, it is treated as the literal method name "*" without special semantics.
Source : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods