CORS issue with Tomcat and Android Webview

后端 未结 5 770
南笙
南笙 2021-02-15 12:38

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-en

相关标签:
5条回答
  • 2021-02-15 13:26

    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.

    0 讨论(0)
  • 2021-02-15 13:27

    Thank you JuergenB, I spent lot of hours looking for this solution for Apache Isis Framework that uses Swagger for API. The problem with Ionic app when you try to connect to API URL is always the same:

    XMLHttpRequest cannot load http://localhost:8080/restful/services/Object/actions/listAll/invoke
    No 'Access-Control-Allow-Origin' header is present on the requested resource.
    Origin 'http://localhost:8100' is therefore not allowed access
    

    Here I share the complete code that I implemented

    \webapp\pom.xml

        <!-- Plugin para evitar CORS con Swagger y Chrome -->
        <dependency>
            <groupId>org.ebaysf.web</groupId>
            <artifactId>cors-filter</artifactId>
            <version>1.0.1</version>
        </dependency>
    

    \webapp\src\main\webapp\WEB-INF\web.xml

    <!-- Filter para plugin org.ebaysf.web.cors.CORSFilter -->
        <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://192.168.1.100:8100,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>/restful/*</url-pattern>
        </filter-mapping>
    

    And then in Ionic project you can do this command

    ionic serve --address=192.168.1.100
    change with your PC IP
    

    You can test your app compiling it to a platform Android/IOS or simply access through your phone to http://yourip:8100

    0 讨论(0)
  • 2021-02-15 13:28

    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 :-)

    0 讨论(0)
  • 2021-02-15 13:35

    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!

    0 讨论(0)
  • 2021-02-15 13:43

    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!

    0 讨论(0)
提交回复
热议问题