Implement Cross Origin Resource Sharing (CORS) on Resteasy + Netty server - java

Integrating Resteasy in my netty 4 + Guice application works perfectly using server adapter provided by Resteasy (great job guys).
Now my JAX-RS server runs on a different port as my HTTP server (also based on Netty).
So, I need to implement Cross Origin Resource Sharing (CORS) on my Resteasy server by adding following HTTP headers to the response:
Access-Control-Allow-Origin : *
Access-Control-Allow-Methods : GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers : X-Requested-With, Content-Type, Content-Length
For now, I have forked NettyJaxrsServer and RestEasyHttpResponseEncoder classes and it works quite good (but not a very "clean" solution to me).
I just wonder how to add those headers to response using something like a customized encoder that I could add to my Netty pipeline (or something else...)
Thanks.

Solution : create a SimpleChannelInboundHandler like this:
public class CorsHeadersChannelHandler extends SimpleChannelInboundHandler<NettyHttpRequest> {
protected void channelRead0(ChannelHandlerContext ctx, NettyHttpRequest request) throws Exception {
request.getResponse().getOutputHeaders().add("Access-Control-Allow-Origin", "*");
request.getResponse().getOutputHeaders().add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
request.getResponse().getOutputHeaders().add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Content-Length");
ctx.fireChannelRead(request);
}
}
and add this to Netty pipeline just before RestEasyHttpResponseEncoder.
NOTE: Finnally I have added RestEasyHttpRequestDecoder, RestEasyHttpResponseEncoder and RequestHandler at the end of my existing Netty Http server pipeline, so I don't need CORS headers anymore.

Related

How to resolve CORS issue in Angular 12?

I am working on an Angular project and I have login page and during submitting login API, I am facing CORS error. I am attaching screenshot of it also. Any suggestion would be appreciated.
API Service.ts:
constructor( private http: HttpClient ) { }
httpOptionsPost = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods' : 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers' : 'Origin, Content-Type, Accept, X-Custom-Header, Upgrade-Insecure-Requests',
})
};
httpOptionsGet = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
})
};
_login(username, password) {
const url = this.domain + '/api/login?username='+username+'&password='+password;
return this.http.post(url, {}, this.httpOptionsPost);
}
Login Component:
this.apiService._login(data.username, data.password).subscribe((resp: any) => {
const resobj = JSON.parse(JSON.stringify(resp));
console.log(resobj);
})
You need to set up proxy configuration for running the app locally.
A short example is:
// create a file proxy.config.json
{
"/api": {
"target": "http://example.com/", // put your actual domain
"secure": false // true or false depending on your needs.
}
}
Then run your project passing the proxy conf to ng-serve or configure it in the package.json.
For instance:
ng serve --proxy-config proxy.conf.json
or
npm start -- --proxy-config proxy.conf.json
Or simply configure it in the angular.json.
Then, in your code, remove the usage of the domain, or assign the domain to localhost using environment.dev.ts if the domain is not the same as where you are going to deploy you app.
I'm referring to the domain variable in this block.
_login(username, password) {
const url = this.domain + '/api/login?username='+username+'&password='+password;
return this.http.post(url, {}, this.httpOptionsPost);
}
More details in https://www.positronx.io/setting-up-angular-proxy-configuration-via-angular-json/ and / or in the official webpack site for advanced proxy configurations
Cors preflight should be handled at server side code. In your case try adding below annotation onto your controller class.
#CrossOrigin("*")
#PostMapping("/user")
public User userController(){
}
And hope you have a configuration class for handling the CORS, if not try adding one. Sample code for reference.
#Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
List<String> allowedMethods=new ArrayList<>();
allowedMethods.add("GET");
allowedMethods.add("POST");
allowedMethods.add("PUT");
allowedMethods.add("DELETE");
CorsConfiguration cors=new CorsConfiguration();
cors.setAllowedMethods(allowedMethods);
http.cors().configurationSource(request -> cors.applyPermitDefaultValues());
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().requestMatchers(EndpointRequest.to("loggers")).hasRole("ADMIN").and().httpBasic();
}}
in your question , the pc_coder answer is right:
cors is related with backend. Be sure you allowed from api to reach requests
like the link:
How does Access-Control-Allow-Origin header work?
I'm not quite familiar with Angular. However, I had the same CORS issue with Vue.js and the solution is to change the add these lines :
module.exports = {
devServer: {
proxy: 'https://yourdomain'
}
}
and also replace the https://yourdomain/login to localhost when you do .get or .post, for example : 'http://localhost:8082/login'
I'm not sure if this would work for you since I am also very new at this, but it did work for me.
Before browser makes a call to your API URL from the angular client application, it will send a preflight request (An HTTP OPTIONS call). this will happen only when your client side application and server side applications are residing in different sub domains or domains. It is the responsibility of the server side application to control whether the cross origin request should be served or not. I see that you are setting the CORS headers in your request header. but it should be received from the API server. so remove that from the angular end and make the proper changes in the server side code.
By default, the Same-Origin-Policy (SOP) forbids request to a different resource. Everything should be loaded from the same source. This is to prevent websites to load data from different servers without the users knowledge. However, there are circumstances where this is required. To cover such case and still provide high safety measurements, CORS was created.
With CORS it is possible to send requests to a server different than the original one.
For this the webserver which is receiving the request must be configured accordingly. In your case this means, you have to adjust the CORS settings in the application which is providing the login API you are calling and allow localhost:4200 to be able to send CORS requests.

Spring not setting statusText on response

I am using some 3rd party stuff that is requiring the statusText on the response to be set. No matter what I do, it is just "". I am using Spring 4.3.4 and Spring Boot 1.4.2.
I tried having the HttpServletResponse as a parameter in my controller method and then call sendError like this:
public void foo(#PathVariable String myVar, HttpServletResponse response) throws IOException
{
//... other stuff
response.sendError(200, "OKOKOKOKOK");
}
However, when the actual response comes back to the browser, the response looks like this:
{"timestamp":1486068130749,"status":200,"error":"OK","message":"OKOKOKOKOK","path":"/my/path"}
And the statusText is still "". I know Spring must be generating this response before sending back to the client. How can I prevent this? Is there some way I can get a non empty string set for the statusText?
I think you must be referring to the HTTP status line sent by the Servlet container.
As of Tomcat 8.5, the HTTP reason phrase is not written anymore, since it's not required and should not be read by HTTP clients: See this Spring Boot issue for more details.
1.4.2 version of Spring-boot uses an embedded tomcat 8.5.6 in which the response text was dropped. So, you are getting statusText as "".
Initially the reason phrase was removed by Tomcat-8.5.
However, later a patch was provided to configure it with an attribute in the Http Connector.
All you have to do is set sendReasonPhrase="true" attribute and it will work as before.
Use any of the below method to configure :
Method-1 : Update the .../apache-tomcat-8.5.x/conf/server.xml to include the attribute as below :
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" sendReasonPhrase="true" />
Method-2 : Configure through bean. (add in the config file)
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(connector ->
((AbstractProtocol) connector.getProtocolHandler()).setSendReasonPhrase(true));
return factory;
}
NOTE : This config will be dropped in Tomcat-9.0 (ie : response message will not be sent)
Refs :
Bug Report & Discussion
Tomcat documentation for configuring the attribute

How to enable Access-Control-Allow-Origin in Undertow?

Try 1
builder.setHandler(new HttpHandler() {
#Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(new HttpString("Access-Control-Allow-Origin"), "*");
exchange.getResponseHeaders().put(new HttpString("Access-Control-Allow-Methods"),
"GET, POST, PUT, DELETE, OPTIONS");
String ss = exchange.getResponseHeaders().get(new HttpString("Access-Control-Allow-Headers"))
.getFirst();
System.out.println(ss);
exchange.getResponseHeaders().put(new HttpString("Access-Control-Allow-Headers"), ss);
}
});
Try 2
HttpHandler enHandler = new SetHeaderHandler("Access-Control-Allow-Origin", "*");
builder.setHandler(enHandler);
I tried this above settings to enable the CORS in my undertow service, but it not working with my angular UI. works fine in postman.
it works after adding these line
ResteasyDeployment rd = new ResteasyDeployment();
CorsFilter filter = new CorsFilter();
filter.setAllowedMethods("GET,POST,PUT,DELETE,OPTIONS");
filter.getAllowedOrigins().add("*");
rd.setProviderFactory(new ResteasyProviderFactory());
rd.getProviderFactory().register(filter);
I've recently had the same issue with the Undertow server's ability to add the Access-Control-Allow-Origin header.
Although not very convenient, I was able to set the header inside of the HttpHandler.handleRequest method as such:
httpServerExchange.getResponseHeaders()
.put(new HttpString("Access-Control-Allow-Origin"), "*");
As described in UNDERTOW-1376, the Undertow community seems to suggest using either undertow-cors-filter or cors-filter as community developed solutions.
When protected resources are accessed by unauthenticated request, Servlet based filters will not have a chance to intercept such requests for sending CORS headers. So, any servlet based filters may not be suitable for such requirements.
Also, the undertow-cors-filter seems to be not updated and not working (when I tried with Wildfly 15). Its failing with regex related errors.
CORS Filter functionality could be achieved in Wildfly using Undertow Predicates & Exchange Attributes. A regex can be used to define the list of domains to be whitelisted. And the CORS related response headers could be written conditionally by checking the incoming Origin header and comparing it against the whitelist using regex matching. All these could be configured in the standalone.xml of Wildfly without any Java coding. The following article explains the same.
https://medium.com/amritatech/a-mechanism-to-enable-cors-filter-with-no-coding-57ef2906e023

Spring Boot + Zuul: how to get the "Content-length" header from a file stream provided by RESTful services?

I am using Spring Cloud and Zuul proxy as gateway to my RESTful service provided by a microservice. When I perform a request directly to an instance of a microservice, all the headers are provided as I expected. However, when the same request is proxied by Zuul, the header "Content-length" is removed. I've made some research about it and I saw that Zuul adds the header "Transfer-Encoding" as "chunked" and in this case the header Content-length is omitted (Content-Length is being stripped, Spring Cloud Netflix: Whats happening in ZuulConfiguration with the ZuulServlet?).
However, I really need to get the "Content-length" provided by my RESTful service. This request also must be proxied by Zuul (I have many instances of a microservice, so I wouldn't access them directly).
Here is the method in my microservice:
#RequestMapping(value = "/jobresult/{id}", method = RequestMethod.GET)
#Timed
public ResponseEntity<InputStreamResource> downloadJobResult(#PathVariable Long id) {
Job job = jobService.findOne(id);
File file = new File(job.getTargetFile());
try {
return ResponseEntity.ok().contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(new InputStreamResource(new FileInputStream(file)));
} catch (FileNotFoundException e) {
log.error(e.getMessage(), e);
}
}
For example, the request to /api/jobresult/1 provides the header "Content-length" correctly, but the request to /service/api/jobresult/1 (routed by Zuul) do not show this header and also modify the "Transfer-Encoding" to "chunked".
The response filter for Zuul from Spring Cloud Netflix code is causing the issue.
Solution
Add an application.properties file in your src/main/resources if you don't have it and add the following line:
zuul.set-content-length=true
Unfortunately I couldn't find a answer for this problem. To make the things work, I wrote an alternative header "X-Content-Length" containing the file size.
In this way, Zuul does not erase the header and I can read it in the client side. As I have the control of both, it is not a problem. But in other cases, the clients should be aware of such header.

Cloud Endpoints: Access Paramters in Servlet Filter

I'm trying to build an api with Google Cloud Endpoints.
As Cloud Endpoints does not provide authentication beside Googles own OAuth I try to build my own. Therefore I want to access the parameters provided for the API (for example #Named("token") token) inside a servlet filter.
Unfortunately I cannot find any of the provided information inside the httpRequest. Is that normal? Is there a possibility to access the parameters?
I would appreciate if someone could help me!
UPDATE:
With the infos from jirungaray I tried to build an authentication using headers but ran into the same problem. Used a REST-Client to send some headers as I could not figure out how to do this with the API Explorer. Inside my filter I try to access the token from the headers:
#Override
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(Constants.AUTH_TOKEN);
...
chain.doFilter(request, response);
}
The reason why I try to do something like this, is that I'm using Guice for Dependency Injection and want my token to be injected inside another object.
With Guice I have the following Provider using the token to inject a FacebookClient (using the token) per request.
#Provides
public FacebookClient getFacebookClientProvider(#Named("fbToken") Provider<String> fbToken) {
return new DefaultFacebookClient(fbToken.get(), Version.VERSION_2_2);
}
As described in the Guice wiki (SevletModule) this uses a sevlet filter to get the information from the request.
Is there any solution to achieve this kind of DI with Cloud Endpoints?
Philip,
Yes, it does makes sense you are getting an empty request. Your endpoint calls are first handled by Google (they receive the API calls) and then those are processed and sent to a handler in your app. As this is all done in the background it's very easy to miss that your endpoints aren't actually getting the same request you sent, they get a completely different request sent from Google's infrastructure.
Even though your approach should work including tokens info in url makes them easier to sniff, even if you use SSL or encrypt your params the token is there in plain sight.
For what you are trying to achieve I recommend you include the token as a header in your request and retrieve that header by accessing the HTTPRequest directly on the endpoint, this is injected automatically if you include an HTTPServletRequest param in you endpoint method.
eg.
public APIResponse doSomething(SomeComplexRquestModel request,
HttpServletRequest rawRequest) {
}
If you still feel you should go with your original approach just comment and I'll help you debug the issue.

Categories

Resources