Files
shz-backend/ruoyi-common/src/main/java/com/ruoyi/common/utils/RequestUrlUtils.java

233 lines
7.8 KiB
Java
Raw Normal View History

package com.ruoyi.common.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
/**
* 请求公开地址工具
*
* <p>应用位于反向代理后时Servlet 请求看到的 serverName/serverPort 可能是代理的内网地址
* 因此优先使用代理传递的公开地址再回退到 Host Servlet 请求信息</p>
*/
public final class RequestUrlUtils
{
private RequestUrlUtils()
{
}
public static String buildBaseUrl(HttpServletRequest request)
{
if (request == null) {
return "";
}
String forwardedHeader = request.getHeader("Forwarded");
String forwarded = firstForwardedValue(forwardedHeader, "host");
String forwardedHost = firstHeaderValue(request.getHeader("X-Forwarded-Host"));
String hostHeader = request.getHeader("Host");
String requestHost = firstNonBlank(hostHeader, request.getServerName());
URI browserOrigin = isInternalHost(requestHost)
? firstBrowserOrigin(request)
: null;
String browserHost = browserOrigin == null ? null : getAuthority(browserOrigin);
boolean hasPublicHost = !isBlank(forwarded) || !isBlank(forwardedHost)
|| !isBlank(hostHeader) || browserOrigin != null;
String host = firstNonBlank(
forwarded,
forwardedHost,
browserHost,
hostHeader,
request.getServerName());
host = normalizeHost(host);
String scheme = firstNonBlank(
firstForwardedValue(request.getHeader("Forwarded"), "proto"),
firstHeaderValue(request.getHeader("X-Forwarded-Proto")),
browserOrigin == null ? null : browserOrigin.getScheme(),
request.getScheme());
scheme = normalizeScheme(scheme, request.getScheme());
String forwardedPort = firstNonBlank(
extractPort(forwarded),
firstHeaderValue(request.getHeader("X-Forwarded-Port")),
browserOrigin == null ? null : String.valueOf(browserOrigin.getPort()));
if ("-1".equals(forwardedPort)) {
forwardedPort = null;
}
if (!hasPort(host) && shouldIncludePort(forwardedPort, scheme)) {
host += ":" + forwardedPort;
}
if (!hasPort(host) && isBlank(forwardedPort) && !hasPublicHost
&& shouldIncludePort(String.valueOf(request.getServerPort()), scheme)) {
host += ":" + request.getServerPort();
}
String contextPath = request.getContextPath();
return scheme + "://" + host + (contextPath == null ? "" : contextPath);
}
/**
* 反向代理未透传 Host Servlet 可能只能看到 127.0.0.1:9091
* 对浏览器请求Origin/Referer 仍然包含用户实际访问的公开地址作为兜底来源
*/
private static URI firstBrowserOrigin(HttpServletRequest request)
{
URI origin = parseHttpUri(firstHeaderValue(request.getHeader("Origin")));
return origin != null
? origin
: parseHttpUri(firstHeaderValue(request.getHeader("Referer")));
}
private static URI parseHttpUri(String value)
{
if (isBlank(value)) {
return null;
}
try {
URI uri = new URI(value.trim());
String scheme = uri.getScheme();
return uri.getHost() != null && ("http".equalsIgnoreCase(scheme)
|| "https".equalsIgnoreCase(scheme))
? uri : null;
} catch (URISyntaxException e) {
return null;
}
}
private static String getAuthority(URI uri)
{
String host = uri.getHost();
if (host == null) {
return null;
}
if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
host = "[" + host + "]";
}
return uri.getPort() > 0 ? host + ":" + uri.getPort() : host;
}
private static boolean isInternalHost(String host)
{
if (isBlank(host)) {
return true;
}
String normalized = host.trim().toLowerCase();
if (normalized.startsWith("[") && normalized.contains("]")) {
normalized = normalized.substring(1, normalized.indexOf(']'));
} else if (normalized.indexOf(':') == normalized.lastIndexOf(':')) {
normalized = normalized.substring(0, normalized.indexOf(':'));
}
return "localhost".equals(normalized)
|| "127.0.0.1".equals(normalized)
|| "0.0.0.0".equals(normalized)
|| "::1".equals(normalized);
}
private static String firstForwardedValue(String header, String name)
{
if (isBlank(header)) {
return null;
}
String firstElement = header.split(",", 2)[0];
String[] parameters = firstElement.split(";");
for (String parameter : parameters) {
String[] pair = parameter.split("=", 2);
if (pair.length == 2 && name.equalsIgnoreCase(pair[0].trim())) {
return stripQuotes(pair[1].trim());
}
}
return null;
}
private static String firstHeaderValue(String header)
{
if (isBlank(header)) {
return null;
}
return stripQuotes(header.split(",", 2)[0].trim());
}
private static String normalizeHost(String host)
{
if (isBlank(host)) {
return "localhost";
}
String normalized = stripQuotes(host.trim());
if (normalized.indexOf('\r') >= 0 || normalized.indexOf('\n') >= 0
|| normalized.indexOf('/') >= 0 || normalized.indexOf('?') >= 0
|| normalized.indexOf('#') >= 0) {
return "localhost";
}
return normalized;
}
private static String normalizeScheme(String scheme, String fallback)
{
if ("https".equalsIgnoreCase(scheme)) {
return "https";
}
if ("http".equalsIgnoreCase(scheme)) {
return "http";
}
return "https".equalsIgnoreCase(fallback) ? "https" : "http";
}
private static String extractPort(String host)
{
if (isBlank(host)) {
return null;
}
String normalized = host.trim();
if (normalized.startsWith("[") && normalized.contains("]:")) {
return normalized.substring(normalized.lastIndexOf(':') + 1);
}
int colon = normalized.lastIndexOf(':');
return colon > -1 && normalized.indexOf(':') == colon
? normalized.substring(colon + 1) : null;
}
private static boolean hasPort(String host)
{
return !isBlank(extractPort(host));
}
private static boolean shouldIncludePort(String port, String scheme)
{
if (isBlank(port)) {
return false;
}
try {
int portNumber = Integer.parseInt(port);
return portNumber > 0 && portNumber <= 65535
&& !(("http".equalsIgnoreCase(scheme) && portNumber == 80)
|| ("https".equalsIgnoreCase(scheme) && portNumber == 443));
} catch (NumberFormatException e) {
return false;
}
}
private static String firstNonBlank(String... values)
{
for (String value : values) {
if (!isBlank(value)) {
return value.trim();
}
}
return null;
}
private static String stripQuotes(String value)
{
if (value != null && value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
}
return value;
}
private static boolean isBlank(String value)
{
return value == null || value.trim().isEmpty();
}
}