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

org.springframework.web.util.ForwardedHeaderUtils Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.web.util;

import java.net.InetSocketAddress;
import java.net.URI;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

/**
 * Utility class to assist with processing "Forwarded" and "X-Forwarded-*" headers.
 *
 * 

Note: There are security considerations surrounding the use * of forwarded headers. Those should not be used unless the application is * behind a trusted proxy that inserts them and also explicitly removes any such * headers coming from an external source. * *

In most cases, you should not use this class directly but rather rely on * {@link org.springframework.web.filter.ForwardedHeaderFilter} for Spring MVC or * {@link org.springframework.web.server.adapter.ForwardedHeaderTransformer} in * order to extract the information from the headers as early as possible and discard * such headers. Underlying servers such as Tomcat, Jetty, and Reactor Netty also * provide options to handle forwarded headers even earlier. * * @author Rossen Stoyanchev * @since 6.1 */ public abstract class ForwardedHeaderUtils { private static final String FORWARDED_VALUE = "\"?([^;,\"]+)\"?"; private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=" + FORWARDED_VALUE); private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=" + FORWARDED_VALUE); private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE); /** * Adapt the scheme+host+port of the given {@link URI} from the "Forwarded" header * (see RFC 7239) or from the * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers if * "Forwarded" is not present. * @param uri the request {@code URI} * @param headers the HTTP headers to consider * @return a {@link UriComponentsBuilder} that reflects the request URI and * additional updates from forwarded headers */ public static UriComponentsBuilder adaptFromForwardedHeaders(URI uri, HttpHeaders headers) { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri); try { String forwardedHeader = headers.getFirst("Forwarded"); if (StringUtils.hasText(forwardedHeader)) { Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedHeader); if (matcher.find()) { uriComponentsBuilder.scheme(matcher.group(1).trim()); uriComponentsBuilder.port(null); } else if (isForwardedSslOn(headers)) { uriComponentsBuilder.scheme("https"); uriComponentsBuilder.port(null); } matcher = FORWARDED_HOST_PATTERN.matcher(forwardedHeader); if (matcher.find()) { adaptForwardedHost(uriComponentsBuilder, matcher.group(1).trim()); } } else { String protocolHeader = headers.getFirst("X-Forwarded-Proto"); if (StringUtils.hasText(protocolHeader)) { uriComponentsBuilder.scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]); uriComponentsBuilder.port(null); } else if (isForwardedSslOn(headers)) { uriComponentsBuilder.scheme("https"); uriComponentsBuilder.port(null); } String hostHeader = headers.getFirst("X-Forwarded-Host"); if (StringUtils.hasText(hostHeader)) { adaptForwardedHost(uriComponentsBuilder, StringUtils.tokenizeToStringArray(hostHeader, ",")[0]); } String portHeader = headers.getFirst("X-Forwarded-Port"); if (StringUtils.hasText(portHeader)) { uriComponentsBuilder.port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0])); } } } catch (NumberFormatException ex) { throw new IllegalArgumentException("Failed to parse a port from \"forwarded\"-type headers. " + "If not behind a trusted proxy, consider using ForwardedHeaderFilter " + "with removeOnly=true. Request headers: " + headers); } uriComponentsBuilder.resetPortIfDefaultForScheme(); return uriComponentsBuilder; } private static boolean isForwardedSslOn(HttpHeaders headers) { String forwardedSsl = headers.getFirst("X-Forwarded-Ssl"); return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on"); } private static void adaptForwardedHost(UriComponentsBuilder uriComponentsBuilder, String rawValue) { int portSeparatorIdx = rawValue.lastIndexOf(':'); int squareBracketIdx = rawValue.lastIndexOf(']'); if (portSeparatorIdx > squareBracketIdx) { if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) { throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue); } uriComponentsBuilder.host(rawValue.substring(0, portSeparatorIdx)); uriComponentsBuilder.port(Integer.parseInt(rawValue, portSeparatorIdx + 1, rawValue.length(), 10)); } else { uriComponentsBuilder.host(rawValue); uriComponentsBuilder.port(null); } } /** * Parse the first "Forwarded: for=..." or "X-Forwarded-For" header value to * an {@code InetSocketAddress} representing the address of the client. * @param uri the request {@code URI} * @param headers the request headers that may contain forwarded headers * @param remoteAddress the current remote address * @return an {@code InetSocketAddress} with the extracted host and port, or * {@code null} if the headers are not present * @see RFC 7239, Section 5.2 */ @Nullable public static InetSocketAddress parseForwardedFor( URI uri, HttpHeaders headers, @Nullable InetSocketAddress remoteAddress) { int port = (remoteAddress != null ? remoteAddress.getPort() : "https".equals(uri.getScheme()) ? 443 : 80); String forwardedHeader = headers.getFirst("Forwarded"); if (StringUtils.hasText(forwardedHeader)) { String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0]; Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse); if (matcher.find()) { String value = matcher.group(1).trim(); String host = value; int portSeparatorIdx = value.lastIndexOf(':'); int squareBracketIdx = value.lastIndexOf(']'); if (portSeparatorIdx > squareBracketIdx) { if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) { throw new IllegalArgumentException("Invalid IPv4 address: " + value); } host = value.substring(0, portSeparatorIdx); try { port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10); } catch (NumberFormatException ex) { throw new IllegalArgumentException( "Failed to parse a port from \"forwarded\"-type header value: " + value); } } return InetSocketAddress.createUnresolved(host, port); } } String forHeader = headers.getFirst("X-Forwarded-For"); if (StringUtils.hasText(forHeader)) { String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0]; return InetSocketAddress.createUnresolved(host, port); } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy