CORS issue with Tomcat and Android Webview

南笙酒味 提交于 2019-12-21 04:28:15

问题


I am facing a strange problem with Tomcat 8 and CORS. I am developing a Hybrid web app using ionicframework, AngularJS, Cordova as front end and Tomcat 8 and Spring 3 as back-end.

For easy development I am testing the functionality in chrome , where things are working fine. I added CORS filter with standard configuration to allow CROSS ORIGIN requests from browser.

Today I converted my app into Android App and started making AJAX calls to tomcat server. To my surprise things stopped working . I debugged further and anomalies in the headers of browser and Android webview.

Browser sends 2 requests for same call OPTION and POST. But Android Webview only send POST request.

Browser Request Headers:

OPTION:

Remote Address:54.254.159.166:80
Request URL:http://medistreet.in/auth2
Request Method:OPTIONS
Status Code:200 OK
Request Headers 
OPTIONS /auth2 HTTP/1.1
Host: medistreet.in
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Access-Control-Request-Headers: accept, content-type
Accept: */*
Referer: http://localhost/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8

POST:

Remote Address:54.254.159.166:80
Request URL:http://medistreet.in/auth2
Request Method:POST
Status Code:200 OK

Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:39
Content-Type:application/json;charset=UTF-8
Host:medistreet.in
Origin:http://localhost
Referer:http://localhost/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36

Android Request Headers:

Request URL:http://medistreet.in/auth2
Request Method:POST
Status Code:403 Forbidden
Request Headers
POST http://medistreet.in/auth2 HTTP/1.1
Accept: application/json, text/plain, */*
Origin: file://
User-Agent: Mozilla/5.0 (Linux; Android 4.4.2; XT1033 Build/KXB20.25-1.31) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
Content-Type: application/json;charset=UTF-8

The difference here I see is with Number of headers and specially Origin Header which contains "file://". To overcome this I added more option is CORS filter:

<filter>
        <filter-name>CorsFilter</filter-name>
        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
            <init-param>
            <param-name>cors.allowed.origins</param-name>
            <param-value>*</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Another strange thing is that when we send the same Android request Headers from POSTMAN (chrome REST plugin) request is successful.

POSTMAN Headers:

Remote Address:54.254.159.166:80
Request URL:http://medistreet.in/auth2
Request Method:POST
Status Code:200 OK
Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Content-Length:39
Content-Type:application/json;charset=UTF-8
Cookie:fbm_464284963672217=base_domain=.medistreet.in; JSESSIONID=87435755F03D7B045DD6E33D1D16AC51; fbsr_464284963672217=dUjASqF-nWquTFPk_-5wAtI0jTImBNkVxglUT-gHNSw.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImNvZGUiOiJBUUQ0UEZZVXE4eDFIa3V6OW9RV3RlVzE4clQ3SmtVRjBzU1VVcXhfV1BENG8tV1BZYjZuTVdDdDJGMmw4TjJUeUxLSzhIYUU1TUc2MkY5cXZOaXRMN3NGdklNZkhySmluYkdjMWs1THAyZnZYa2Zpa1lLVGJ0OWlZeXVvRDNWUDhTblp4czJCeTQ4RTlYY1ZjUmhGWGJsNnFMeG5YcWxxQ0d3b0hRM1ctRWhlLU02ejVITnhhakJtaVFRVk9PanFBVUtMSlk4Y3pLa0RtejFSY3RjTEFRaW16X1lkLUFkUngxUGwzajVNczdWOFdiMW9xeC05QjA0T2xraXktVU9ZalpSRUJsZjhibnZjQXQ2aUZTc1d2QTA3TjVUYnFIekVxQ0JIYjJNRG4tSUJhajl6TEMwQlVpckM0YzJXbC1GVDNhcyIsImlzc3VlZF9hdCI6MTM5ODE4MDg2NCwidXNlcl9pZCI6IjU3NjI1MjI2MiJ9
Host:medistreet.in
Origin:chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36

After this also there is no solution to the problem . I suspect that Android Webview is not sending something which Tomcat is rejecting.

Any help will highly be appreciated.


回答1:


I did more research on this and figure out the issue.If you see the headers from Android and look into Origin Header.

Origin: file://

Tomcat CORS filter tries to validate the URI in Origin header and considers "file://" as an invalid URI and returns back 403.

     */
    protected static boolean isValidOrigin(String origin) {
       /* // Checks for encoded characters. Helps prevent CRLF injection.
        if (origin.contains("%")) {
            return false;
        }

        URI originURI;

        try {
            originURI = new URI(origin);
        } catch (URISyntaxException e) {
            return false;
        }
        // If scheme for URI is null, return false. Return true otherwise.
        return originURI.getScheme() != null;
*/
        return true;
    }

I need to dig more on why Android is sending the incorrect URI.




回答2:


I just had the same problem. I may provide my solution. It is probably not the better way to do it as it involved to have access to the tomcat server configuration.

I defined a new filter which will set to null the desired header fields:

First, define the custom filter:

public class HttpHeaderNullifierFilter implements Filter {

/**
 * init property name defining the headers names and values to set to null
 */
public static final String HEADERS_PROPERTY = "headers";

/**
 * the names/values separator in the HEADERS_PROPERTY property
 */
public static final String HEADERS_SEPARATOR_PROPERTY = ",";

/**
 * the key-value separator in the HEADERS_PROPERTY property
 */
public static final String HEADERS_KEY_VALUE_SEPARATOR_PROPERTY = "=";

/**
 * the origin-header's names/values to set to null
 */
private Map<String,Set<String>> headersNamesValuesToNullify;

/**
 * the request wrapper. override the specified fields with a null value
 */
public class CustomServletRequestWrapper extends HttpServletRequestWrapper {

    /**
     * constructor: wrap the request
     * 
     * @param request
     */
    public CustomServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * Check the header value: if the header-names'list contain the
     * specified header, null is returned
     */
    public String getHeader(String headerName) {
        String result = super.getHeader(headerName);
        if (headersNamesValuesToNullify.containsKey(headerName)) {
            if(result != null && headersNamesValuesToNullify.get(headerName).contains(result)){
                return null;
            }
        }
        return result;
    }
}

@Override
public void destroy() {
    // TODO Auto-generated method stub

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
        FilterChain filterChain) throws IOException, ServletException {
    if (!(servletRequest instanceof HttpServletRequest)
            || !(servletResponse instanceof HttpServletResponse)) {
        throw new ServletException("no HTTP request");
    }
    CustomServletRequestWrapper requestWrapper = new CustomServletRequestWrapper(
            (HttpServletRequest) servletRequest);
    // Forward the request down the filter chain.
    filterChain.doFilter(requestWrapper, servletResponse);
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    headersNamesValuesToNullify = new HashMap<String,Set<String>>();
    if (filterConfig != null) {
        String configAllowedOrigins = filterConfig.getInitParameter(HEADERS_PROPERTY);
        if (configAllowedOrigins != null && configAllowedOrigins.length() > 0) {
            if (configAllowedOrigins.indexOf(HEADERS_SEPARATOR_PROPERTY) > 0) {
                for (String value : configAllowedOrigins.split(HEADERS_SEPARATOR_PROPERTY)) {
                    addKeyValueToMap(value, headersNamesValuesToNullify);
                }
            } else {
                addKeyValueToMap(configAllowedOrigins, headersNamesValuesToNullify);
            }
        }
    }
}

/**
 * add the key-value par to the map
 * @param keyValueStringValue the key-pair as one value
 * @param map the map to add the key-pair value
 */
private void addKeyValueToMap(String keyValueStringValue, Map<String, Set<String>> map){
    if(keyValueStringValue != null && keyValueStringValue.indexOf(HEADERS_KEY_VALUE_SEPARATOR_PROPERTY) > 0){
        String[] keyValueSplit = keyValueStringValue.split(HEADERS_KEY_VALUE_SEPARATOR_PROPERTY);
        String key = keyValueSplit[0];
        String value = keyValueSplit[1];
        if(! map.containsKey(key)){
            map.put(key, new HashSet<String>());
        }
        map.get(key).add(value);
    }
}
}

This class define an "CustomServletRequestWrapper" class which will wrap the "HttpServletRequest". This wrapper override the "getHeader" method to implement our own behaviour: return a null value.

Then, when the filter receive a request, it wraps it and continue the filter-chain. Finally, when the tomcat's CORS-filter will access the "Origin" header-field, it will get a null value, a skip the CORS process.

This filter should be configured in the web.xml descriptor file as below :

<filter>
    <filter-name>CordovaOriginWrapper</filter-name>
    <filter-class>your.package.HttpHeaderNullifierFilter</filter-class>
    <init-param>
        <param-name>headers</param-name>
        <param-value>Origin=file://</param-value>
    </init-param>
</filter>
<!-- others filters may be declared-->
<filter>
    <filter-name>CorsFilter</filter-name>
...
</filter>
...

<!-- match the URLs fo use the filter: in this example, all URLs are matched using the pattern '/*'-->
<filter-mapping>
    <filter-name>CordovaOriginWrapper</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

(it should be defined before the tomcat CORS-filter to be executed in the same order).

Hope it may help!




回答3:


I had the same problem.My solution is that my tomcat version is 7.x,it does not support http header “Origin=file://”,i changed my tomcat version to 8.x,and it works for me.

Hope it may help!




回答4:


There is an alternative solution by using a further CORS class, different from the one of Tomcat. You can find the Java class at CORS filter.

To install it, down the GITHUB file and unzip it. Create a new package in your Java project (call it "org.ebaysf.web.cors"). Copy the Java class "CORSFilter" into this package. Then, in web.xml of your project replace the old CORS section with this one:

<!-- Filter for CORS extension due to IONIC framework -->
<filter>
    <filter-name>CORS Filter</filter-name>
    <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
    <init-param>
        <param-name>cors.allowed.origins</param-name>
        <param-value>http://localhost,https://localhost,http://localhost:8100,https://localhost:8100,file://</param-value>
    </init-param>
    <init-param>
        <param-name>cors.allowed.methods</param-name>
        <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
    </init-param>
    <init-param>
        <param-name>cors.allowed.headers</param-name>
        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
    </init-param>
    <init-param>
        <param-name>cors.exposed.headers</param-name>
        <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials,Authorization</param-value>
    </init-param>
    <init-param>
        <param-name>cors.support.credentials</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>cors.preflight.maxage</param-name>
        <param-value>1800</param-value>
    </init-param>
    <init-param>
        <param-name>cors.logging.enabled</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CORS Filter</filter-name>
    <url-pattern>/rest/*</url-pattern>
</filter-mapping>

Have a closer look at my "cors.allowed.origins" parameter. You need this for IONIC. (Note: with this filter it is not allowed to use cors.allowed.origins = "*"). Adapt the "url-pattern" to your needs.

Hopefully, in a future Tomcat version, this work-around won't be needed anymore :-)



来源:https://stackoverflow.com/questions/23224508/cors-issue-with-tomcat-and-android-webview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!