Thursday, July 6, 2017

HTTP access control (CORS)


In the case that there is a need to post to a different domain by using JavaScript, you may see CORS issue with error such as:


XMLHttpRequest cannot load https://xyz. 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' is therefore not allowed access.



XMLHttpRequest cannot load https://xyz. request header field content-type is not allowed by access-control-allow-headers in preflight response.


When you search this issue and many will suggest adding a header:
"Access-Control-Allow-Origin", "*"

However this is not enough, the following is a code from Swagger example that should be able to handle normal situations:


package io.swagger.sample.util;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class ApiOriginFilter implements javax.servlet.Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
    HttpServletResponse res = (HttpServletResponse) response;
    res.addHeader("Access-Control-Allow-Origin", "*");
    res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
    res.addHeader("Access-Control-Allow-Headers", "Content-Type");
    chain.doFilter(request, response);
  }

  @Override
  public void destroy() {
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }
}




Here is another suggestion:


response.addHeader("Access-Control-Allow-Origin", "*");response.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT");response.addHeader("Access-Control-Allow-Credentials", "true");response.addHeader("Access-Control-Allow-Headers","Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, X-Atmosphere-Transport, *");response.addHeader("Access-Control-Request-Headers","Origin, X-Atmosphere-tracking-id, X-Atmosphere-Framework, X-Cache-Date, Content-Type, X-Atmosphere-Transport, *");


A web page to test RESTFUL JSON post:

<!DOCTYPE html>
<html lang="en">
<head>
<title>RESTFUL API TEST</title>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/json2/20160511/json2.min.js"></script>
</head>
<body>
<div class="container">
  <h1>RESTFUL SERVICE ENDPOINT</h1>
  <form id="form" enctype='application/json' method="post"
    action="https://hostname:port/myapp/v3/api/endpoint">
    <fieldset>
        <legend>Enter sessionid and username</legend>
        <label>session id</label>
        <input name="sessionid" id="sessionid" type="text">
        <label>username</label>
        <input name="username" id="username" type="text">
        <button class="btn" id="submit" type="submit">submit</button>
    </fieldset>
  </form>
</div>
<div class="container">
  <fieldset>
    <legend>Response</legend>
    <label>status</label><input name="status" id="status" type="text">
    <p>
    <textarea id="responseJson" name="responseJson" rows="8" cols="80"></textarea>
  </fieldset>
</div>
       <script type="text/javascript">
       $("form").submit(function(e) {
            // stop the regular form submission
            e.preventDefault();

             // collect the form data while iterating over the inputs
             var data = {};
             for (var i = 0, ii = form.length; i < ii; ++i) {
                 var input = form[i];
                 if (input.name) {
                     data[input.name] = input.value;
                 }
             }

             // construct an HTTP request
             var xhr = new XMLHttpRequest();
             xhr.open(form.method, form.action, true);
             xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
             xhr.onreadystatechange = function() {
                 $("input[name='status']").val(xhr.status);
                 if (xhr.readyState === 4
                         && (xhr.status === 201
                                 || xhr.status === 400 || xhr.status === 500)) {
                     console.log(xhr.responseText);
                     $("textarea[name='responseJson']").val(
                             xhr.responseText);
                 }
             };

             // send the collected data as JSON
             xhr.send(JSON.stringify(data));
    });
    </script>
</body>
</html>



Sometimes, more constraints may be required to limit the domains that could access the resource:


response.addHeader("Access-Control-Allow-Origin", "myhost.mydomain");


Multiple values are not allowed here. 

What you may do here is, in the servlet filter, checks the original against a list of the domains that are allowed to access the resources, then put the original domain in the  "Access-Control-Allow-Origin" header then return it to the client. 

No comments:

Post a Comment