All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.filters.CorsFilter Maven / Gradle / Ivy

There is a newer version: 11.0.0-M24
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.filters;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import jakarta.servlet.FilterChain;
import jakarta.servlet.GenericFilter;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.http.RequestUtil;
import org.apache.tomcat.util.http.ResponseUtil;
import org.apache.tomcat.util.res.StringManager;

/**
 * 

* A {@link jakarta.servlet.Filter} that enable client-side cross-origin requests by implementing W3C's CORS * (Cross-Origin Resource Sharing) specification for resources. Each * {@link HttpServletRequest} request is inspected as per specification, and appropriate response headers are added to * {@link HttpServletResponse}. *

*

* By default, it also sets following request attributes, that help to determine the nature of the request downstream. *

*
    *
  • cors.isCorsRequest: Flag to determine if the request is a CORS request. Set to true if a CORS * request; false otherwise.
  • *
  • cors.request.origin: The Origin URL, i.e. the URL of the page from where the request is originated.
  • *
  • cors.request.type: Type of request. Possible values: *
      *
    • SIMPLE: A request which is not preceded by a pre-flight request.
    • *
    • ACTUAL: A request which is preceded by a pre-flight request.
    • *
    • PRE_FLIGHT: A pre-flight request.
    • *
    • NOT_CORS: A normal same-origin request.
    • *
    • INVALID_CORS: A cross-origin request which is invalid.
    • *
    *
  • *
  • cors.request.headers: Request headers sent as 'Access-Control-Request-Headers' header, for pre-flight * request.
  • *
* If you extend this class and override one or more of the getXxx() methods, consider whether you also need to override * {@link CorsFilter#doFilter(ServletRequest, ServletResponse, FilterChain)} and add appropriate locking so that the * {@code doFilter()} method executes with a consistent configuration. * * @see CORS specification */ public class CorsFilter extends GenericFilter { private static final long serialVersionUID = 1L; private static final StringManager sm = StringManager.getManager(CorsFilter.class); private transient Log log = LogFactory.getLog(CorsFilter.class); // must not be static /** * A {@link Collection} of origins consisting of zero or more origins that are allowed access to the resource. */ private final Collection allowedOrigins = new HashSet<>(); /** * Determines if any origin is allowed to make request. */ private boolean anyOriginAllowed; /** * A {@link Collection} of methods consisting of zero or more methods that are supported by the resource. */ private final Collection allowedHttpMethods = new HashSet<>(); /** * A {@link Collection} of headers consisting of zero or more header field names that are supported by the resource. */ private final Collection allowedHttpHeaders = new HashSet<>(); /** * A {@link Collection} of exposed headers consisting of zero or more header field names of headers other than the * simple response headers that the resource might use and can be exposed. */ private final Collection exposedHeaders = new HashSet<>(); /** * A supports credentials flag that indicates whether the resource supports user credentials in the request. It is * true when the resource does and false otherwise. */ private boolean supportsCredentials; /** * Indicates (in seconds) how long the results of a pre-flight request can be cached in a pre-flight result cache. */ private long preflightMaxAge; /** * Determines if the request should be decorated or not. */ private boolean decorateRequest; @Override public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) { throw new ServletException(sm.getString("corsFilter.onlyHttp")); } // Safe to downcast at this point. HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // Determines the CORS request type. CorsFilter.CORSRequestType requestType = checkRequestType(request); // Adds CORS specific attributes to request. if (isDecorateRequest()) { decorateCORSProperties(request, requestType); } switch (requestType) { case SIMPLE: // Handles a Simple CORS request. case ACTUAL: // Handles an Actual CORS request. this.handleSimpleCORS(request, response, filterChain); break; case PRE_FLIGHT: // Handles a Pre-flight CORS request. this.handlePreflightCORS(request, response, filterChain); break; case NOT_CORS: // Handles a Normal request that is not a cross-origin request. this.handleNonCORS(request, response, filterChain); break; default: // Handles a CORS request that violates specification. this.handleInvalidCORS(request, response, filterChain); break; } } @Override public void init() throws ServletException { parseAndStore(getInitParameter(PARAM_CORS_ALLOWED_ORIGINS, DEFAULT_ALLOWED_ORIGINS), getInitParameter(PARAM_CORS_ALLOWED_METHODS, DEFAULT_ALLOWED_HTTP_METHODS), getInitParameter(PARAM_CORS_ALLOWED_HEADERS, DEFAULT_ALLOWED_HTTP_HEADERS), getInitParameter(PARAM_CORS_EXPOSED_HEADERS, DEFAULT_EXPOSED_HEADERS), getInitParameter(PARAM_CORS_SUPPORT_CREDENTIALS, DEFAULT_SUPPORTS_CREDENTIALS), getInitParameter(PARAM_CORS_PREFLIGHT_MAXAGE, DEFAULT_PREFLIGHT_MAXAGE), getInitParameter(PARAM_CORS_REQUEST_DECORATE, DEFAULT_DECORATE_REQUEST)); } /** * This method returns the parameter's value if it exists, or defaultValue if not. * * @param name The parameter's name * @param defaultValue The default value to return if the parameter does not exist * * @return The parameter's value or the default value if the parameter does not exist */ private String getInitParameter(String name, String defaultValue) { String value = getInitParameter(name); if (value != null) { return value; } return defaultValue; } /** * Handles a CORS request of type {@link CORSRequestType}.SIMPLE. * * @param request The {@link HttpServletRequest} object. * @param response The {@link HttpServletResponse} object. * @param filterChain The {@link FilterChain} object. * * @throws IOException an IO error occurred * @throws ServletException Servlet error propagation * * @see Simple Cross-Origin Request, Actual Request, and * Redirects */ protected void handleSimpleCORS(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws IOException, ServletException { CorsFilter.CORSRequestType requestType = checkRequestType(request); if (!(requestType == CorsFilter.CORSRequestType.SIMPLE || requestType == CorsFilter.CORSRequestType.ACTUAL)) { throw new IllegalArgumentException(sm.getString("corsFilter.wrongType2", CorsFilter.CORSRequestType.SIMPLE, CorsFilter.CORSRequestType.ACTUAL)); } final String origin = request.getHeader(REQUEST_HEADER_ORIGIN); final String method = request.getMethod(); // Section 6.1.2 if (!isOriginAllowed(origin)) { handleInvalidCORS(request, response, filterChain); return; } if (!getAllowedHttpMethods().contains(method)) { handleInvalidCORS(request, response, filterChain); return; } addStandardHeaders(request, response); // Forward the request down the filter chain. filterChain.doFilter(request, response); } /** * Handles CORS pre-flight request. * * @param request The {@link HttpServletRequest} object. * @param response The {@link HttpServletResponse} object. * @param filterChain The {@link FilterChain} object. * * @throws IOException an IO error occurred * @throws ServletException Servlet error propagation */ protected void handlePreflightCORS(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws IOException, ServletException { CORSRequestType requestType = checkRequestType(request); if (requestType != CORSRequestType.PRE_FLIGHT) { throw new IllegalArgumentException(sm.getString("corsFilter.wrongType1", CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH))); } final String origin = request.getHeader(REQUEST_HEADER_ORIGIN); // Section 6.2.2 if (!isOriginAllowed(origin)) { handleInvalidCORS(request, response, filterChain); return; } // Section 6.2.3 String accessControlRequestMethod = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD); if (accessControlRequestMethod == null) { handleInvalidCORS(request, response, filterChain); return; } else { accessControlRequestMethod = accessControlRequestMethod.trim(); } // Section 6.2.4 String accessControlRequestHeadersHeader = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); List accessControlRequestHeaders = new ArrayList<>(); if (accessControlRequestHeadersHeader != null && !accessControlRequestHeadersHeader.trim().isEmpty()) { String[] headers = accessControlRequestHeadersHeader.trim().split(","); for (String header : headers) { accessControlRequestHeaders.add(header.trim().toLowerCase(Locale.ENGLISH)); } } // Section 6.2.5 if (!getAllowedHttpMethods().contains(accessControlRequestMethod)) { handleInvalidCORS(request, response, filterChain); return; } // Section 6.2.6 if (!accessControlRequestHeaders.isEmpty()) { for (String header : accessControlRequestHeaders) { if (!getAllowedHttpHeaders().contains(header)) { handleInvalidCORS(request, response, filterChain); return; } } } addStandardHeaders(request, response); // Do not forward the request down the filter chain. } /** * Handles a request, that's not a CORS request, but is a valid request i.e. it is not a cross-origin request. This * implementation, just forwards the request down the filter chain. * * @param request The {@link HttpServletRequest} object. * @param response The {@link HttpServletResponse} object. * @param filterChain The {@link FilterChain} object. * * @throws IOException an IO error occurred * @throws ServletException Servlet error propagation */ private void handleNonCORS(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws IOException, ServletException { if (!isAnyOriginAllowed()) { // If only specific origins are allowed, the response will vary by // origin ResponseUtil.addVaryFieldName(response, REQUEST_HEADER_ORIGIN); } // Let request pass. filterChain.doFilter(request, response); } /** * Handles a CORS request that violates specification. * * @param request The {@link HttpServletRequest} object. * @param response The {@link HttpServletResponse} object. * @param filterChain The {@link FilterChain} object. */ private void handleInvalidCORS(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) { String origin = request.getHeader(REQUEST_HEADER_ORIGIN); String method = request.getMethod(); String accessControlRequestHeaders = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); response.setContentType("text/plain"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.resetBuffer(); if (log.isDebugEnabled()) { sm.getString("corsFilter.invalidRequest", origin, method, accessControlRequestHeaders); } } /* * Sets a standard set of headers to reduce response variation which in turn is intended to aid caching. */ private void addStandardHeaders(final HttpServletRequest request, final HttpServletResponse response) { final String method = request.getMethod(); final String origin = request.getHeader(REQUEST_HEADER_ORIGIN); // Local copy to avoid concurrency issues if isAnyOriginAllowed() // is overridden. boolean anyOriginAllowed = isAnyOriginAllowed(); if (!anyOriginAllowed) { // If only specific origins are allowed, the response will vary by // origin ResponseUtil.addVaryFieldName(response, REQUEST_HEADER_ORIGIN); } // CORS requests (SIMPLE, ACTUAL, PRE_FLIGHT) set the following headers // although non-CORS requests do not need to. The headers are always set // as a) they do no harm in the non-CORS case and b) it allows the same // response to be cached for CORS and non-CORS requests. // Add a single Access-Control-Allow-Origin header. if (anyOriginAllowed) { // If any origin is allowed, return header with '*'. response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } else { // Add a single Access-Control-Allow-Origin header, with the value // of the Origin header as value. response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, origin); } // If the resource supports credentials, add a single // Access-Control-Allow-Credentials header with the case-sensitive // string "true" as value. if (isSupportsCredentials()) { response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); } // If the list of exposed headers is not empty add one or more // Access-Control-Expose-Headers headers, with as values the header // field names given in the list of exposed headers. // Local copy to avoid concurrency issues if getExposedHeaders() // is overridden. Collection exposedHeaders = getExposedHeaders(); if (exposedHeaders != null && exposedHeaders.size() > 0) { String exposedHeadersString = join(exposedHeaders, ","); response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, exposedHeadersString); } if ("OPTIONS".equals(method)) { // For an OPTIONS request, the response will vary based on the // value or absence of the following headers. Hence they need be be // included in the Vary header. ResponseUtil.addVaryFieldName(response, REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD); ResponseUtil.addVaryFieldName(response, REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); // CORS PRE_FLIGHT (OPTIONS) requests set the following headers although // non-CORS OPTIONS requests do not need to. The headers are always set // as a) they do no harm in the non-CORS case and b) it allows the same // response to be cached for CORS and non-CORS requests. // Local copy to avoid concurrency issues if getPreflightMaxAge() // is overridden. long preflightMaxAge = getPreflightMaxAge(); if (preflightMaxAge > 0) { response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE, String.valueOf(preflightMaxAge)); } // Local copy to avoid concurrency issues if getAllowedHttpMethods() // is overridden. Collection allowedHttpMethods = getAllowedHttpMethods(); if (allowedHttpMethods != null && !allowedHttpMethods.isEmpty()) { response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS, join(allowedHttpMethods, ",")); } // Local copy to avoid concurrency issues if getAllowedHttpHeaders() // is overridden. Collection allowedHttpHeaders = getAllowedHttpHeaders(); if (allowedHttpHeaders != null && !allowedHttpHeaders.isEmpty()) { response.addHeader(RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, join(allowedHttpHeaders, ",")); } } } /** * Decorates the {@link HttpServletRequest}, with CORS attributes. *
    *
  • cors.isCorsRequest: Flag to determine if request is a CORS request. Set to true if CORS * request; false otherwise.
  • *
  • cors.request.origin: The Origin URL.
  • *
  • cors.request.type: Type of request. Values: simple or preflight or * not_cors or invalid_cors
  • *
  • cors.request.headers: Request headers sent as 'Access-Control-Request-Headers' header, for pre-flight * request.
  • *
* * @param request The {@link HttpServletRequest} object. * @param corsRequestType The {@link CORSRequestType} object. */ protected static void decorateCORSProperties(final HttpServletRequest request, final CORSRequestType corsRequestType) { if (request == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequest")); } if (corsRequestType == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequestType")); } switch (corsRequestType) { case SIMPLE: case ACTUAL: request.setAttribute(HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, Boolean.TRUE); request.setAttribute(HTTP_REQUEST_ATTRIBUTE_ORIGIN, request.getHeader(REQUEST_HEADER_ORIGIN)); request.setAttribute(HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE, corsRequestType.name().toLowerCase(Locale.ENGLISH)); break; case PRE_FLIGHT: request.setAttribute(HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, Boolean.TRUE); request.setAttribute(HTTP_REQUEST_ATTRIBUTE_ORIGIN, request.getHeader(REQUEST_HEADER_ORIGIN)); request.setAttribute(HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE, corsRequestType.name().toLowerCase(Locale.ENGLISH)); String headers = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); if (headers == null) { headers = ""; } request.setAttribute(HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS, headers); break; case NOT_CORS: request.setAttribute(HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, Boolean.FALSE); break; default: // Don't set any attributes break; } } /** * Joins elements of {@link Set} into a string, where each element is separated by the provided separator. * * @param elements The {@link Set} containing elements to join together. * @param joinSeparator The character to be used for separating elements. * * @return The joined {@link String}; null if elements {@link Set} is null. */ protected static String join(final Collection elements, final String joinSeparator) { String separator = ","; if (elements == null) { return null; } if (joinSeparator != null) { separator = joinSeparator; } StringBuilder buffer = new StringBuilder(); boolean isFirst = true; for (String element : elements) { if (!isFirst) { buffer.append(separator); } else { isFirst = false; } if (element != null) { buffer.append(element); } } return buffer.toString(); } /** * Determines the request type. * * @param request The HTTP Servlet request * * @return the CORS type */ protected CORSRequestType checkRequestType(final HttpServletRequest request) { CORSRequestType requestType = CORSRequestType.INVALID_CORS; if (request == null) { throw new IllegalArgumentException(sm.getString("corsFilter.nullRequest")); } String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN); // Section 6.1.1 and Section 6.2.1 if (originHeader != null) { if (originHeader.isEmpty()) { requestType = CORSRequestType.INVALID_CORS; } else if (!RequestUtil.isValidOrigin(originHeader)) { requestType = CORSRequestType.INVALID_CORS; } else if (RequestUtil.isSameOrigin(request, originHeader)) { return CORSRequestType.NOT_CORS; } else { String method = request.getMethod(); if (method != null) { if ("OPTIONS".equals(method)) { String accessControlRequestMethodHeader = request.getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD); if (accessControlRequestMethodHeader != null && !accessControlRequestMethodHeader.isEmpty()) { requestType = CORSRequestType.PRE_FLIGHT; } else if (accessControlRequestMethodHeader != null && accessControlRequestMethodHeader.isEmpty()) { requestType = CORSRequestType.INVALID_CORS; } else { requestType = CORSRequestType.ACTUAL; } } else if ("GET".equals(method) || "HEAD".equals(method)) { requestType = CORSRequestType.SIMPLE; } else if ("POST".equals(method)) { String mediaType = getMediaType(request.getContentType()); if (mediaType != null) { if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES.contains(mediaType)) { requestType = CORSRequestType.SIMPLE; } else { requestType = CORSRequestType.ACTUAL; } } } else { requestType = CORSRequestType.ACTUAL; } } } } else { requestType = CORSRequestType.NOT_CORS; } return requestType; } /** * Return the lower case, trimmed value of the media type from the content type. */ private String getMediaType(String contentType) { if (contentType == null) { return null; } String result = contentType.toLowerCase(Locale.ENGLISH); int firstSemiColonIndex = result.indexOf(';'); if (firstSemiColonIndex > -1) { result = result.substring(0, firstSemiColonIndex); } result = result.trim(); return result; } /** * Checks if the Origin is allowed to make a CORS request. * * @param origin The Origin. * * @return true if origin is allowed; false otherwise. */ private boolean isOriginAllowed(final String origin) { if (isAnyOriginAllowed()) { return true; } // If 'Origin' header is a case-sensitive match of any of allowed // origins, then return true, else return false. return getAllowedOrigins().contains(origin); } /** * Parses each param-value and populates configuration variables. If a param is provided, it overrides the default. * * @param allowedOrigins A {@link String} of comma separated origins. * @param allowedHttpMethods A {@link String} of comma separated HTTP methods. * @param allowedHttpHeaders A {@link String} of comma separated HTTP headers. * @param exposedHeaders A {@link String} of comma separated headers that needs to be exposed. * @param supportsCredentials "true" if support credentials needs to be enabled. * @param preflightMaxAge The amount of seconds the user agent is allowed to cache the result of the pre-flight * request. * * @throws ServletException If the configuration is invalid */ private void parseAndStore(final String allowedOrigins, final String allowedHttpMethods, final String allowedHttpHeaders, final String exposedHeaders, final String supportsCredentials, final String preflightMaxAge, final String decorateRequest) throws ServletException { if (allowedOrigins.trim().equals("*")) { this.anyOriginAllowed = true; } else { this.anyOriginAllowed = false; Set setAllowedOrigins = parseStringToSet(allowedOrigins); this.allowedOrigins.clear(); this.allowedOrigins.addAll(setAllowedOrigins); } Set setAllowedHttpMethods = parseStringToSet(allowedHttpMethods); this.allowedHttpMethods.clear(); this.allowedHttpMethods.addAll(setAllowedHttpMethods); Set setAllowedHttpHeaders = parseStringToSet(allowedHttpHeaders); Set lowerCaseHeaders = new HashSet<>(); for (String header : setAllowedHttpHeaders) { String lowerCase = header.toLowerCase(Locale.ENGLISH); lowerCaseHeaders.add(lowerCase); } this.allowedHttpHeaders.clear(); this.allowedHttpHeaders.addAll(lowerCaseHeaders); Set setExposedHeaders = parseStringToSet(exposedHeaders); this.exposedHeaders.clear(); this.exposedHeaders.addAll(setExposedHeaders); // For any value other than 'true' this will be false. this.supportsCredentials = Boolean.parseBoolean(supportsCredentials); if (this.supportsCredentials && this.anyOriginAllowed) { throw new ServletException(sm.getString("corsFilter.invalidSupportsCredentials")); } try { if (!preflightMaxAge.isEmpty()) { this.preflightMaxAge = Long.parseLong(preflightMaxAge); } else { this.preflightMaxAge = 0L; } } catch (NumberFormatException e) { throw new ServletException(sm.getString("corsFilter.invalidPreflightMaxAge"), e); } // For any value other than 'true' this will be false. this.decorateRequest = Boolean.parseBoolean(decorateRequest); } /** * Takes a comma separated list and returns a Set<String>. * * @param data A comma separated list of strings. * * @return Set$lt;String> */ private Set parseStringToSet(final String data) { String[] splits; if (data != null && data.length() > 0) { splits = data.split(","); } else { splits = new String[] {}; } Set set = new HashSet<>(); if (splits.length > 0) { for (String split : splits) { set.add(split.trim()); } } return set; } /** * Determines if any origin is allowed to make CORS request. * * @return true if it's enabled; false otherwise. */ public boolean isAnyOriginAllowed() { return anyOriginAllowed; } /** * Obtain the headers to expose. * * @return the headers that should be exposed by browser. */ public Collection getExposedHeaders() { return exposedHeaders; } /** * Determines is supports credentials is enabled. * * @return true if the use of credentials is supported otherwise false */ public boolean isSupportsCredentials() { return supportsCredentials; } /** * Returns the preflight response cache time in seconds. * * @return Time to cache in seconds. */ public long getPreflightMaxAge() { return preflightMaxAge; } /** * Returns the {@link Set} of allowed origins that are allowed to make requests. * * @return {@link Set} */ public Collection getAllowedOrigins() { return allowedOrigins; } /** * Returns a {@link Set} of HTTP methods that are allowed to make requests. * * @return {@link Set} */ public Collection getAllowedHttpMethods() { return allowedHttpMethods; } /** * Returns a {@link Set} of headers support by resource. * * @return {@link Set} */ public Collection getAllowedHttpHeaders() { return allowedHttpHeaders; } /** * Should CORS specific attributes be added to the request. * * @return {@code true} if the request should be decorated, otherwise {@code false} */ public boolean isDecorateRequest() { return decorateRequest; } /* * Log objects are not Serializable but this Filter is because it extends GenericFilter. Tomcat won't serialize a * Filter but in case something else does... */ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); log = LogFactory.getLog(CorsFilter.class); } // -------------------------------------------------- CORS Response Headers /** * The Access-Control-Allow-Origin header indicates whether a resource can be shared based by returning the value of * the Origin request header in the response. */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; /** * The Access-Control-Allow-Credentials header indicates whether the response to request can be exposed when the * omit credentials flag is unset. When part of the response to a preflight request it indicates that the actual * request can include user credentials. */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; /** * The Access-Control-Expose-Headers header indicates which headers are safe to expose to the API of a CORS API * specification */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; /** * The Access-Control-Max-Age header indicates how long the results of a preflight request can be cached in a * preflight result cache. */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; /** * The Access-Control-Allow-Methods header indicates, as part of the response to a preflight request, which methods * can be used during the actual request. */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; /** * The Access-Control-Allow-Headers header indicates, as part of the response to a preflight request, which header * field names can be used during the actual request. */ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; // -------------------------------------------------- CORS Request Headers /** * The Origin header indicates where the cross-origin request or preflight request originates from. */ public static final String REQUEST_HEADER_ORIGIN = "Origin"; /** * The Access-Control-Request-Method header indicates which method will be used in the actual request as part of the * preflight request. */ public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; /** * The Access-Control-Request-Headers header indicates which headers will be used in the actual request as part of * the preflight request. */ public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; // ----------------------------------------------------- Request attributes /** * The prefix to a CORS request attribute. */ public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors."; /** * Attribute that contains the origin of the request. */ public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN = HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.origin"; /** * Boolean value, suggesting if the request is a CORS request or not. */ public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST = HTTP_REQUEST_ATTRIBUTE_PREFIX + "isCorsRequest"; /** * Type of CORS request, of type {@link CORSRequestType}. */ public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE = HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.type"; /** * Request headers sent as 'Access-Control-Request-Headers' header, for pre-flight request. */ public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS = HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.headers"; // -------------------------------------------------------------- Constants /** * Enumerates varies types of CORS requests. Also, provides utility methods to determine the request type. */ protected enum CORSRequestType { /** * A simple HTTP request, i.e. it shouldn't be pre-flighted. */ SIMPLE, /** * An HTTP request that needs to be pre-flighted. */ ACTUAL, /** * A pre-flight CORS request, to get meta information, before a non-simple HTTP request is sent. */ PRE_FLIGHT, /** * Not a CORS request, but a normal request. */ NOT_CORS, /** * An invalid CORS request, i.e. it qualifies to be a CORS request, but fails to be a valid one. */ INVALID_CORS } /** * {@link Collection} of media type values for the Content-Type header that will be treated as 'simple'. Note * media-type values are compared ignoring parameters and in a case-insensitive manner. * * @see http://www.w3.org/TR/cors/#terminology */ public static final Collection SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("application/x-www-form-urlencoded", "multipart/form-data", "text/plain"))); // ------------------------------------------------ Configuration Defaults /** * By default, no origins are allowed to make requests. */ public static final String DEFAULT_ALLOWED_ORIGINS = ""; /** * By default, following methods are supported: GET, POST, HEAD and OPTIONS. */ public static final String DEFAULT_ALLOWED_HTTP_METHODS = "GET,POST,HEAD,OPTIONS"; /** * By default, time duration to cache pre-flight response is 30 mins. */ public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800"; /** * By default, support credentials is disabled. */ public static final String DEFAULT_SUPPORTS_CREDENTIALS = "false"; /** * By default, following headers are supported: Origin,Accept,X-Requested-With, Content-Type, * Access-Control-Request-Method, and Access-Control-Request-Headers. */ public static final String DEFAULT_ALLOWED_HTTP_HEADERS = "Origin,Accept,X-Requested-With,Content-Type," + "Access-Control-Request-Method,Access-Control-Request-Headers"; /** * By default, none of the headers are exposed in response. */ public static final String DEFAULT_EXPOSED_HEADERS = ""; /** * By default, request is decorated with CORS attributes. */ public static final String DEFAULT_DECORATE_REQUEST = "true"; // ----------------------------------------Filter Config Init param-name(s) /** * Key to retrieve allowed origins from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_ALLOWED_ORIGINS = "cors.allowed.origins"; /** * Key to retrieve support credentials from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_SUPPORT_CREDENTIALS = "cors.support.credentials"; /** * Key to retrieve exposed headers from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_EXPOSED_HEADERS = "cors.exposed.headers"; /** * Key to retrieve allowed headers from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_ALLOWED_HEADERS = "cors.allowed.headers"; /** * Key to retrieve allowed methods from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_ALLOWED_METHODS = "cors.allowed.methods"; /** * Key to retrieve preflight max age from {@link jakarta.servlet.FilterConfig}. */ public static final String PARAM_CORS_PREFLIGHT_MAXAGE = "cors.preflight.maxage"; /** * Key to determine if request should be decorated. */ public static final String PARAM_CORS_REQUEST_DECORATE = "cors.request.decorate"; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy