feat: introduce ServerHttpHeaderValidator replacing ServerTransportSecurityValidator
Same feature set as ServerTransportSecurityValidator but uses a lazy "header accessor" function to read header values instead of extracting all headers into a map. Matches more closely what modern frameworks and JDK APIs do. Fixes #870 Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
parent
2bb1481ec3
commit
5be2fa0688
11 changed files with 470 additions and 168 deletions
|
|
@ -7,6 +7,7 @@ package io.modelcontextprotocol.server.transport;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import io.modelcontextprotocol.util.Assert;
|
import io.modelcontextprotocol.util.Assert;
|
||||||
|
|
||||||
|
|
@ -22,7 +23,8 @@ import io.modelcontextprotocol.util.Assert;
|
||||||
* @see ServerTransportSecurityValidator
|
* @see ServerTransportSecurityValidator
|
||||||
* @see ServerTransportSecurityException
|
* @see ServerTransportSecurityException
|
||||||
*/
|
*/
|
||||||
public final class DefaultServerTransportSecurityValidator implements ServerTransportSecurityValidator {
|
public final class DefaultServerTransportSecurityValidator
|
||||||
|
implements ServerTransportSecurityValidator, ServerHttpHeaderValidator {
|
||||||
|
|
||||||
private static final String ORIGIN_HEADER = "Origin";
|
private static final String ORIGIN_HEADER = "Origin";
|
||||||
|
|
||||||
|
|
@ -47,27 +49,24 @@ public final class DefaultServerTransportSecurityValidator implements ServerTran
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public void validateHeaders(Map<String, List<String>> headers) throws ServerTransportSecurityException {
|
public void validateHeaders(Map<String, List<String>> headers) throws ServerTransportSecurityException {
|
||||||
boolean missingHost = true;
|
validate(new MapHeaderAccessor(headers));
|
||||||
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
}
|
||||||
if (ORIGIN_HEADER.equalsIgnoreCase(entry.getKey())) {
|
|
||||||
List<String> values = entry.getValue();
|
@Override
|
||||||
if (values == null || values.isEmpty()) {
|
public void validate(HeaderAccessor headerAccessor) throws ServerTransportSecurityException {
|
||||||
throw new ServerTransportSecurityException(403, "Invalid Origin header");
|
List<String> originValues = headerAccessor.getHeader(ORIGIN_HEADER);
|
||||||
}
|
if (originValues != null && !originValues.isEmpty()) {
|
||||||
validateOrigin(values.get(0));
|
validateOrigin(originValues.get(0));
|
||||||
}
|
|
||||||
else if (HOST_HEADER.equalsIgnoreCase(entry.getKey())) {
|
|
||||||
missingHost = false;
|
|
||||||
List<String> values = entry.getValue();
|
|
||||||
if (values == null || values.isEmpty()) {
|
|
||||||
throw new ServerTransportSecurityException(421, "Invalid Host header");
|
|
||||||
}
|
|
||||||
validateHost(values.get(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!allowedHosts.isEmpty() && missingHost) {
|
|
||||||
throw new ServerTransportSecurityException(421, "Invalid Host header");
|
if (!allowedHosts.isEmpty()) {
|
||||||
|
List<String> hostValues = headerAccessor.getHeader(HOST_HEADER);
|
||||||
|
if (hostValues == null || hostValues.isEmpty()) {
|
||||||
|
throw new ServerTransportSecurityException(421, "Invalid Host header");
|
||||||
|
}
|
||||||
|
validateHost(hostValues.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +138,37 @@ public final class DefaultServerTransportSecurityValidator implements ServerTran
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HeaderAccessor} view over a {@code Map<String, List<String>>}, used to
|
||||||
|
* bridge the deprecated {@link #validateHeaders(Map)} to
|
||||||
|
* {@link #validate(HeaderAccessor)}.
|
||||||
|
*/
|
||||||
|
private static final class MapHeaderAccessor implements HeaderAccessor {
|
||||||
|
|
||||||
|
private final Map<String, List<String>> headers;
|
||||||
|
|
||||||
|
private MapHeaderAccessor(Map<String, List<String>> headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeader(String name) {
|
||||||
|
return headers.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(entry -> entry.getKey().equalsIgnoreCase(name))
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaderNames() {
|
||||||
|
return List.copyOf(headers.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for creating instances of {@link DefaultServerTransportSecurityValidator}.
|
* Builder for creating instances of {@link DefaultServerTransportSecurityValidator}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026-2026 the original author or authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.modelcontextprotocol.server.transport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for accessing HTTP headers from an incoming request. Implementations should
|
||||||
|
* provide case-insensitive header name lookups (e.g., when backed by
|
||||||
|
* {@code HttpServletRequest}).
|
||||||
|
*
|
||||||
|
* @author Neeraj Bhatt
|
||||||
|
* @since 2.1.0
|
||||||
|
* @see ServerHttpHeaderValidator
|
||||||
|
*/
|
||||||
|
public interface HeaderAccessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values of the specified header, or an empty list if the header is not
|
||||||
|
* present.
|
||||||
|
* @param name the header name (case-insensitive)
|
||||||
|
* @return the list of header values, never {@code null}
|
||||||
|
*/
|
||||||
|
List<String> getHeader(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all header names present in the request.
|
||||||
|
* @return the list of header names, never {@code null}
|
||||||
|
*/
|
||||||
|
List<String> getHeaderNames();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026-2026 the original author or authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.modelcontextprotocol.server.transport;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HeaderAccessor} implementation backed by an {@link HttpServletRequest}. Header
|
||||||
|
* name lookups are case-insensitive as per the Servlet specification.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* For internal use only.
|
||||||
|
*
|
||||||
|
* @author Neeraj Bhatt
|
||||||
|
* @since 2.1.0
|
||||||
|
* @see HeaderAccessor
|
||||||
|
*/
|
||||||
|
final class HttpServletHeaderAccessor implements HeaderAccessor {
|
||||||
|
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
|
||||||
|
HttpServletHeaderAccessor(HttpServletRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeader(String name) {
|
||||||
|
return Collections.list(this.request.getHeaders(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaderNames() {
|
||||||
|
return Collections.list(this.request.getHeaderNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,6 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
|
@ -26,21 +21,6 @@ final class HttpServletRequestUtils {
|
||||||
private HttpServletRequestUtils() {
|
private HttpServletRequestUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts all headers from the HTTP request into a map.
|
|
||||||
* @param request The HTTP servlet request
|
|
||||||
* @return A map of header names to their values
|
|
||||||
*/
|
|
||||||
static Map<String, List<String>> extractHeaders(HttpServletRequest request) {
|
|
||||||
Map<String, List<String>> headers = new HashMap<>();
|
|
||||||
Enumeration<String> names = request.getHeaderNames();
|
|
||||||
while (names.hasMoreElements()) {
|
|
||||||
String name = names.nextElement();
|
|
||||||
headers.put(name, Collections.list(request.getHeaders(name)));
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the request body, decoded using the request's character encoding (or UTF-8 if
|
* Reads the request body, decoded using the request's character encoding (or UTF-8 if
|
||||||
* not specified), while bounding the number of bytes read.
|
* not specified), while bounding the number of bytes read.
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
/**
|
/**
|
||||||
* Security validator for validating HTTP requests.
|
* Security validator for validating HTTP requests.
|
||||||
*/
|
*/
|
||||||
private final ServerTransportSecurityValidator securityValidator;
|
private final ServerHttpHeaderValidator httpHeaderValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
|
* Creates a new HttpServletSseServerTransportProvider instance with a custom SSE
|
||||||
|
|
@ -174,20 +174,20 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
* @param keepAliveInterval The interval for keep-alive pings, or null to disable
|
* @param keepAliveInterval The interval for keep-alive pings, or null to disable
|
||||||
* keep-alive functionality
|
* keep-alive functionality
|
||||||
* @param contextExtractor The extractor for transport context from the request.
|
* @param contextExtractor The extractor for transport context from the request.
|
||||||
* @param securityValidator The security validator for validating HTTP requests.
|
* @param httpHeaderValidator The HTTP header validator for validating HTTP requests.
|
||||||
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
||||||
* positive.
|
* positive.
|
||||||
*/
|
*/
|
||||||
private HttpServletSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
|
private HttpServletSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
|
||||||
String sseEndpoint, Duration keepAliveInterval,
|
String sseEndpoint, Duration keepAliveInterval,
|
||||||
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
||||||
ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
|
ServerHttpHeaderValidator httpHeaderValidator, int requestMaxSize) {
|
||||||
|
|
||||||
Assert.notNull(jsonMapper, "JsonMapper must not be null");
|
Assert.notNull(jsonMapper, "JsonMapper must not be null");
|
||||||
Assert.notNull(messageEndpoint, "messageEndpoint must not be null");
|
Assert.notNull(messageEndpoint, "messageEndpoint must not be null");
|
||||||
Assert.notNull(sseEndpoint, "sseEndpoint must not be null");
|
Assert.notNull(sseEndpoint, "sseEndpoint must not be null");
|
||||||
Assert.notNull(contextExtractor, "Context extractor must not be null");
|
Assert.notNull(contextExtractor, "Context extractor must not be null");
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
|
|
@ -195,7 +195,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
this.messageEndpoint = messageEndpoint;
|
this.messageEndpoint = messageEndpoint;
|
||||||
this.sseEndpoint = sseEndpoint;
|
this.sseEndpoint = sseEndpoint;
|
||||||
this.contextExtractor = contextExtractor;
|
this.contextExtractor = contextExtractor;
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
this.requestMaxSize = requestMaxSize;
|
this.requestMaxSize = requestMaxSize;
|
||||||
|
|
||||||
if (keepAliveInterval != null) {
|
if (keepAliveInterval != null) {
|
||||||
|
|
@ -293,8 +293,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -371,8 +370,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -619,7 +617,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
|
|
||||||
private Duration keepAliveInterval;
|
private Duration keepAliveInterval;
|
||||||
|
|
||||||
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
|
private ServerHttpHeaderValidator httpHeaderValidator = ServerHttpHeaderValidator.NOOP;
|
||||||
|
|
||||||
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
||||||
|
|
||||||
|
|
@ -702,10 +700,25 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
* @param securityValidator The security validator to use. Must not be null.
|
* @param securityValidator The security validator to use. Must not be null.
|
||||||
* @return This builder instance
|
* @return This builder instance
|
||||||
* @throws IllegalArgumentException if securityValidator is null
|
* @throws IllegalArgumentException if securityValidator is null
|
||||||
|
* @deprecated Use {@link #httpHeaderValidator(ServerHttpHeaderValidator)}
|
||||||
|
* instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(securityValidator, "Security validator must not be null");
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = ServerTransportSecurityValidator.toHttpHeaderValidator(securityValidator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HTTP header validator for validating HTTP requests.
|
||||||
|
* @param httpHeaderValidator The HTTP header validator to use. Must not be null.
|
||||||
|
* @return This builder instance
|
||||||
|
* @throws IllegalArgumentException if httpHeaderValidator is null
|
||||||
|
*/
|
||||||
|
public Builder httpHeaderValidator(ServerHttpHeaderValidator httpHeaderValidator) {
|
||||||
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -734,7 +747,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
|
||||||
}
|
}
|
||||||
return new HttpServletSseServerTransportProvider(
|
return new HttpServletSseServerTransportProvider(
|
||||||
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, baseUrl, messageEndpoint,
|
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, baseUrl, messageEndpoint,
|
||||||
sseEndpoint, keepAliveInterval, contextExtractor, securityValidator, requestMaxSize);
|
sseEndpoint, keepAliveInterval, contextExtractor, httpHeaderValidator, requestMaxSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ package io.modelcontextprotocol.server.transport;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -68,7 +67,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
/**
|
/**
|
||||||
* Security validator for validating HTTP requests.
|
* Security validator for validating HTTP requests.
|
||||||
*/
|
*/
|
||||||
private final ServerTransportSecurityValidator securityValidator;
|
private final ServerHttpHeaderValidator httpHeaderValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum size, in bytes, of a single request body accepted by this transport.
|
* Maximum size, in bytes, of a single request body accepted by this transport.
|
||||||
|
|
@ -82,24 +81,24 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
* @param mcpEndpoint The endpoint URI where clients should send their JSON-RPC
|
* @param mcpEndpoint The endpoint URI where clients should send their JSON-RPC
|
||||||
* messages.
|
* messages.
|
||||||
* @param contextExtractor The extractor for transport context from the request.
|
* @param contextExtractor The extractor for transport context from the request.
|
||||||
* @param securityValidator The security validator for validating HTTP requests.
|
* @param httpHeaderValidator The HTTP header validator for validating HTTP requests.
|
||||||
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
||||||
* positive.
|
* positive.
|
||||||
* @throws IllegalArgumentException if any parameter is null
|
* @throws IllegalArgumentException if any parameter is null
|
||||||
*/
|
*/
|
||||||
private HttpServletStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
|
private HttpServletStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
|
||||||
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
||||||
ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
|
ServerHttpHeaderValidator httpHeaderValidator, int requestMaxSize) {
|
||||||
Assert.notNull(jsonMapper, "jsonMapper must not be null");
|
Assert.notNull(jsonMapper, "jsonMapper must not be null");
|
||||||
Assert.notNull(mcpEndpoint, "mcpEndpoint must not be null");
|
Assert.notNull(mcpEndpoint, "mcpEndpoint must not be null");
|
||||||
Assert.notNull(contextExtractor, "contextExtractor must not be null");
|
Assert.notNull(contextExtractor, "contextExtractor must not be null");
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.mcpEndpoint = mcpEndpoint;
|
this.mcpEndpoint = mcpEndpoint;
|
||||||
this.contextExtractor = contextExtractor;
|
this.contextExtractor = contextExtractor;
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
this.requestMaxSize = requestMaxSize;
|
this.requestMaxSize = requestMaxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,8 +161,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -300,7 +298,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (
|
private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (
|
||||||
serverRequest) -> McpTransportContext.EMPTY;
|
serverRequest) -> McpTransportContext.EMPTY;
|
||||||
|
|
||||||
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
|
private ServerHttpHeaderValidator httpHeaderValidator = ServerHttpHeaderValidator.NOOP;
|
||||||
|
|
||||||
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
||||||
|
|
||||||
|
|
@ -354,10 +352,25 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
* @param securityValidator The security validator to use. Must not be null.
|
* @param securityValidator The security validator to use. Must not be null.
|
||||||
* @return this builder instance
|
* @return this builder instance
|
||||||
* @throws IllegalArgumentException if securityValidator is null
|
* @throws IllegalArgumentException if securityValidator is null
|
||||||
|
* @deprecated Use {@link #httpHeaderValidator(ServerHttpHeaderValidator)}
|
||||||
|
* instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(securityValidator, "Security validator must not be null");
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = ServerTransportSecurityValidator.toHttpHeaderValidator(securityValidator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HTTP header validator for validating HTTP requests.
|
||||||
|
* @param httpHeaderValidator The HTTP header validator to use. Must not be null.
|
||||||
|
* @return this builder instance
|
||||||
|
* @throws IllegalArgumentException if httpHeaderValidator is null
|
||||||
|
*/
|
||||||
|
public Builder httpHeaderValidator(ServerHttpHeaderValidator httpHeaderValidator) {
|
||||||
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,7 +397,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
|
||||||
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
|
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
|
||||||
return new HttpServletStatelessServerTransport(
|
return new HttpServletStatelessServerTransport(
|
||||||
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, contextExtractor,
|
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, contextExtractor,
|
||||||
securityValidator, requestMaxSize);
|
httpHeaderValidator, requestMaxSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import java.io.PrintWriter;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
|
@ -131,7 +130,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
/**
|
/**
|
||||||
* Security validator for validating HTTP requests.
|
* Security validator for validating HTTP requests.
|
||||||
*/
|
*/
|
||||||
private final ServerTransportSecurityValidator securityValidator;
|
private final ServerHttpHeaderValidator httpHeaderValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new HttpServletStreamableServerTransportProvider instance.
|
* Constructs a new HttpServletStreamableServerTransportProvider instance.
|
||||||
|
|
@ -143,25 +142,25 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
* @param contextExtractor The extractor for transport context from the request.
|
* @param contextExtractor The extractor for transport context from the request.
|
||||||
* @param keepAliveInterval The interval for keep-alive pings. If null, no keep-alive
|
* @param keepAliveInterval The interval for keep-alive pings. If null, no keep-alive
|
||||||
* will be scheduled.
|
* will be scheduled.
|
||||||
* @param securityValidator The security validator for validating HTTP requests.
|
* @param httpHeaderValidator The HTTP header validator for validating HTTP requests.
|
||||||
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
|
||||||
* positive.
|
* positive.
|
||||||
* @throws IllegalArgumentException if any parameter is null
|
* @throws IllegalArgumentException if any parameter is null
|
||||||
*/
|
*/
|
||||||
private HttpServletStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
|
private HttpServletStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
|
||||||
boolean disallowDelete, McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
boolean disallowDelete, McpTransportContextExtractor<HttpServletRequest> contextExtractor,
|
||||||
Duration keepAliveInterval, ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
|
Duration keepAliveInterval, ServerHttpHeaderValidator httpHeaderValidator, int requestMaxSize) {
|
||||||
Assert.notNull(jsonMapper, "JsonMapper must not be null");
|
Assert.notNull(jsonMapper, "JsonMapper must not be null");
|
||||||
Assert.notNull(mcpEndpoint, "MCP endpoint must not be null");
|
Assert.notNull(mcpEndpoint, "MCP endpoint must not be null");
|
||||||
Assert.notNull(contextExtractor, "Context extractor must not be null");
|
Assert.notNull(contextExtractor, "Context extractor must not be null");
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
|
||||||
|
|
||||||
this.jsonMapper = jsonMapper;
|
this.jsonMapper = jsonMapper;
|
||||||
this.mcpEndpoint = mcpEndpoint;
|
this.mcpEndpoint = mcpEndpoint;
|
||||||
this.disallowDelete = disallowDelete;
|
this.disallowDelete = disallowDelete;
|
||||||
this.contextExtractor = contextExtractor;
|
this.contextExtractor = contextExtractor;
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
this.requestMaxSize = requestMaxSize;
|
this.requestMaxSize = requestMaxSize;
|
||||||
|
|
||||||
if (keepAliveInterval != null) {
|
if (keepAliveInterval != null) {
|
||||||
|
|
@ -275,8 +274,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -415,8 +413,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -594,8 +591,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
|
this.httpHeaderValidator.validate(new HttpServletHeaderAccessor(request));
|
||||||
this.securityValidator.validateHeaders(headers);
|
|
||||||
}
|
}
|
||||||
catch (ServerTransportSecurityException e) {
|
catch (ServerTransportSecurityException e) {
|
||||||
response.sendError(e.getStatusCode(), e.getMessage());
|
response.sendError(e.getStatusCode(), e.getMessage());
|
||||||
|
|
@ -848,7 +844,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
|
|
||||||
private Duration keepAliveInterval;
|
private Duration keepAliveInterval;
|
||||||
|
|
||||||
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
|
private ServerHttpHeaderValidator httpHeaderValidator = ServerHttpHeaderValidator.NOOP;
|
||||||
|
|
||||||
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
|
||||||
|
|
||||||
|
|
@ -916,10 +912,25 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
* @param securityValidator The security validator to use. Must not be null.
|
* @param securityValidator The security validator to use. Must not be null.
|
||||||
* @return this builder instance
|
* @return this builder instance
|
||||||
* @throws IllegalArgumentException if securityValidator is null
|
* @throws IllegalArgumentException if securityValidator is null
|
||||||
|
* @deprecated Use {@link #httpHeaderValidator(ServerHttpHeaderValidator)}
|
||||||
|
* instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
public Builder securityValidator(ServerTransportSecurityValidator securityValidator) {
|
||||||
Assert.notNull(securityValidator, "Security validator must not be null");
|
Assert.notNull(securityValidator, "Security validator must not be null");
|
||||||
this.securityValidator = securityValidator;
|
this.httpHeaderValidator = ServerTransportSecurityValidator.toHttpHeaderValidator(securityValidator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HTTP header validator for validating HTTP requests.
|
||||||
|
* @param httpHeaderValidator The HTTP header validator to use. Must not be null.
|
||||||
|
* @return this builder instance
|
||||||
|
* @throws IllegalArgumentException if httpHeaderValidator is null
|
||||||
|
*/
|
||||||
|
public Builder httpHeaderValidator(ServerHttpHeaderValidator httpHeaderValidator) {
|
||||||
|
Assert.notNull(httpHeaderValidator, "HTTP header validator must not be null");
|
||||||
|
this.httpHeaderValidator = httpHeaderValidator;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,7 +957,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
|
||||||
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
|
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
|
||||||
return new HttpServletStreamableServerTransportProvider(
|
return new HttpServletStreamableServerTransportProvider(
|
||||||
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, disallowDelete,
|
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, disallowDelete,
|
||||||
contextExtractor, keepAliveInterval, securityValidator, requestMaxSize);
|
contextExtractor, keepAliveInterval, httpHeaderValidator, requestMaxSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2026-2026 the original author or authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.modelcontextprotocol.server.transport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates HTTP request headers in server transports.
|
||||||
|
*
|
||||||
|
* @author Neeraj Bhatt
|
||||||
|
* @see HeaderAccessor
|
||||||
|
* @see ServerTransportSecurityException
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ServerHttpHeaderValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A no-op validator that accepts all requests without validation.
|
||||||
|
*/
|
||||||
|
ServerHttpHeaderValidator NOOP = headerAccessor -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the HTTP headers from an incoming request.
|
||||||
|
* @param headerAccessor provides access to request headers
|
||||||
|
* @throws ServerTransportSecurityException if validation fails
|
||||||
|
*/
|
||||||
|
void validate(HeaderAccessor headerAccessor) throws ServerTransportSecurityException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ package io.modelcontextprotocol.server.transport;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for validating HTTP requests in server transports. Implementations can
|
* Interface for validating HTTP requests in server transports. Implementations can
|
||||||
|
|
@ -13,9 +15,12 @@ import java.util.Map;
|
||||||
* to the MCP specification.
|
* to the MCP specification.
|
||||||
*
|
*
|
||||||
* @author Daniel Garnier-Moiroux
|
* @author Daniel Garnier-Moiroux
|
||||||
* @see DefaultServerTransportSecurityValidator
|
* @see ServerHttpHeaderValidator
|
||||||
* @see ServerTransportSecurityException
|
* @see ServerTransportSecurityException
|
||||||
|
* @deprecated Use {@link ServerHttpHeaderValidator} instead. This interface will be
|
||||||
|
* removed in a future major version.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ServerTransportSecurityValidator {
|
public interface ServerTransportSecurityValidator {
|
||||||
|
|
||||||
|
|
@ -25,6 +30,20 @@ public interface ServerTransportSecurityValidator {
|
||||||
ServerTransportSecurityValidator NOOP = headers -> {
|
ServerTransportSecurityValidator NOOP = headers -> {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapts a legacy map-based validator to a header accessor validator.
|
||||||
|
* @param validator the legacy validator to adapt
|
||||||
|
* @return a header accessor validator delegating to {@code validator}
|
||||||
|
*/
|
||||||
|
static ServerHttpHeaderValidator toHttpHeaderValidator(ServerTransportSecurityValidator validator) {
|
||||||
|
return accessor -> {
|
||||||
|
var collectedHeaders = accessor.getHeaderNames()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toUnmodifiableMap(Function.identity(), accessor::getHeader));
|
||||||
|
validator.validateHeaders(collectedHeaders);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the HTTP headers from an incoming request.
|
* Validates the HTTP headers from an incoming request.
|
||||||
* @param headers A map of header names to their values (multi-valued headers
|
* @param headers A map of header names to their values (multi-valued headers
|
||||||
|
|
|
||||||
|
|
@ -43,50 +43,50 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originHeaderMissing() {
|
void originHeaderMissing() {
|
||||||
assertThatCode(() -> validator.validateHeaders(new HashMap<>())).doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(emptyAccessor())).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originHeaderListEmpty() {
|
void originHeaderListEmpty() {
|
||||||
assertThatThrownBy(() -> validator.validateHeaders(Map.of("Origin", List.of()))).isEqualTo(INVALID_ORIGIN);
|
assertThatCode(() -> validator.validate(headerAccessor())).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void caseInsensitive() {
|
void caseInsensitive() {
|
||||||
var headers = Map.of("origin", List.of("http://localhost:8080"));
|
var accessor = headerAccessor("Origin", "http://localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void exactMatch() {
|
void exactMatch() {
|
||||||
var headers = originHeader("http://localhost:8080");
|
var accessor = originAccessor("http://localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentPort() {
|
void differentPort() {
|
||||||
|
|
||||||
var headers = originHeader("http://localhost:3000");
|
var accessor = originAccessor("http://localhost:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> validator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> validator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentHost() {
|
void differentHost() {
|
||||||
|
|
||||||
var headers = originHeader("http://example.com:8080");
|
var accessor = originAccessor("http://example.com:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> validator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> validator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentScheme() {
|
void differentScheme() {
|
||||||
|
|
||||||
var headers = originHeader("https://localhost:8080");
|
var accessor = originAccessor("https://localhost:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> validator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> validator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|
@ -99,37 +99,37 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void anyPortWithWildcard() {
|
void anyPortWithWildcard() {
|
||||||
var headers = originHeader("http://localhost:3000");
|
var accessor = originAccessor("http://localhost:3000");
|
||||||
|
|
||||||
assertThatCode(() -> wildcardValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> wildcardValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noPortWithWildcard() {
|
void noPortWithWildcard() {
|
||||||
var headers = originHeader("http://localhost");
|
var accessor = originAccessor("http://localhost");
|
||||||
|
|
||||||
assertThatCode(() -> wildcardValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> wildcardValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentPortWithWildcard() {
|
void differentPortWithWildcard() {
|
||||||
var headers = originHeader("http://localhost:8080");
|
var accessor = originAccessor("http://localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> wildcardValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> wildcardValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentHostWithWildcard() {
|
void differentHostWithWildcard() {
|
||||||
var headers = originHeader("http://example.com:3000");
|
var accessor = originAccessor("http://example.com:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> wildcardValidator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> wildcardValidator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentSchemeWithWildcard() {
|
void differentSchemeWithWildcard() {
|
||||||
var headers = originHeader("https://localhost:3000");
|
var accessor = originAccessor("https://localhost:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> wildcardValidator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> wildcardValidator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -146,23 +146,23 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void matchingOneOfMultiple() {
|
void matchingOneOfMultiple() {
|
||||||
var headers = originHeader("http://example.com:3000");
|
var accessor = originAccessor("http://example.com:3000");
|
||||||
|
|
||||||
assertThatCode(() -> multipleOriginsValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> multipleOriginsValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void matchingWildcardInMultiple() {
|
void matchingWildcardInMultiple() {
|
||||||
var headers = originHeader("http://myapp.example.com:9999");
|
var accessor = originAccessor("http://myapp.example.com:9999");
|
||||||
|
|
||||||
assertThatCode(() -> multipleOriginsValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> multipleOriginsValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void notMatchingAny() {
|
void notMatchingAny() {
|
||||||
var headers = originHeader("http://malicious.example.com:1234");
|
var accessor = originAccessor("http://malicious.example.com:1234");
|
||||||
|
|
||||||
assertThatThrownBy(() -> multipleOriginsValidator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> multipleOriginsValidator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -176,9 +176,9 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
.allowedOrigins(List.of("http://localhost:8080", "http://example.com:*"))
|
.allowedOrigins(List.of("http://localhost:8080", "http://example.com:*"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
var headers = originHeader("http://example.com:3000");
|
var accessor = originAccessor("http://example.com:3000");
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -188,11 +188,11 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
.allowedOrigins(List.of("http://example.com:*", "http://test.com:3000"))
|
.allowedOrigins(List.of("http://example.com:*", "http://test.com:3000"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(originHeader("http://localhost:8080")))
|
assertThatCode(() -> validator.validate(originAccessor("http://localhost:8080")))
|
||||||
.doesNotThrowAnyException();
|
.doesNotThrowAnyException();
|
||||||
assertThatCode(() -> validator.validateHeaders(originHeader("http://example.com:9999")))
|
assertThatCode(() -> validator.validate(originAccessor("http://example.com:9999")))
|
||||||
.doesNotThrowAnyException();
|
.doesNotThrowAnyException();
|
||||||
assertThatCode(() -> validator.validateHeaders(originHeader("http://test.com:3000")))
|
assertThatCode(() -> validator.validate(originAccessor("http://test.com:3000")))
|
||||||
.doesNotThrowAnyException();
|
.doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,45 +210,45 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void notConfigured() {
|
void notConfigured() {
|
||||||
assertThatCode(() -> validator.validateHeaders(new HashMap<>())).doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(emptyAccessor())).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void missing() {
|
void missing() {
|
||||||
assertThatThrownBy(() -> hostValidator.validateHeaders(new HashMap<>())).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> hostValidator.validate(emptyAccessor())).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listEmpty() {
|
void listEmpty() {
|
||||||
assertThatThrownBy(() -> hostValidator.validateHeaders(Map.of("Host", List.of()))).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> hostValidator.validate(headerAccessor())).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void caseInsensitive() {
|
void caseInsensitive() {
|
||||||
var headers = Map.of("host", List.of("localhost:8080"));
|
var accessor = headerAccessor("Host", "localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> hostValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> hostValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void exactMatch() {
|
void exactMatch() {
|
||||||
var headers = hostHeader("localhost:8080");
|
var accessor = hostAccessor("localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> hostValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> hostValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentPort() {
|
void differentPort() {
|
||||||
var headers = hostHeader("localhost:3000");
|
var accessor = hostAccessor("localhost:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> hostValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> hostValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentHost() {
|
void differentHost() {
|
||||||
var headers = hostHeader("example.com:8080");
|
var accessor = hostAccessor("example.com:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> hostValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> hostValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|
@ -261,23 +261,23 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void anyPort() {
|
void anyPort() {
|
||||||
var headers = hostHeader("localhost:3000");
|
var accessor = hostAccessor("localhost:3000");
|
||||||
|
|
||||||
assertThatCode(() -> wildcardHostValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> wildcardHostValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noPort() {
|
void noPort() {
|
||||||
var headers = hostHeader("localhost");
|
var accessor = hostAccessor("localhost");
|
||||||
|
|
||||||
assertThatCode(() -> wildcardHostValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> wildcardHostValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentHost() {
|
void differentHost() {
|
||||||
var headers = hostHeader("example.com:3000");
|
var accessor = hostAccessor("example.com:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> wildcardHostValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> wildcardHostValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -293,30 +293,30 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void exactMatch() {
|
void exactMatch() {
|
||||||
var headers = hostHeader("example.com:3000");
|
var accessor = hostAccessor("example.com:3000");
|
||||||
|
|
||||||
assertThatCode(() -> multipleHostsValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> multipleHostsValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void wildcard() {
|
void wildcard() {
|
||||||
var headers = hostHeader("myapp.example.com:9999");
|
var accessor = hostAccessor("myapp.example.com:9999");
|
||||||
|
|
||||||
assertThatCode(() -> multipleHostsValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
assertThatCode(() -> multipleHostsValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentHost() {
|
void differentHost() {
|
||||||
var headers = hostHeader("malicious.example.com:3000");
|
var accessor = hostAccessor("malicious.example.com:3000");
|
||||||
|
|
||||||
assertThatThrownBy(() -> multipleHostsValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> multipleHostsValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void differentPort() {
|
void differentPort() {
|
||||||
var headers = hostHeader("localhost:8080");
|
var accessor = hostAccessor("localhost:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> multipleHostsValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> multipleHostsValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -330,10 +330,8 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
.allowedHosts(List.of("localhost:8080", "example.com:*"))
|
.allowedHosts(List.of("localhost:8080", "example.com:*"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(hostHeader("example.com:3000")))
|
assertThatCode(() -> validator.validate(hostAccessor("example.com:3000"))).doesNotThrowAnyException();
|
||||||
.doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(hostAccessor("localhost:8080"))).doesNotThrowAnyException();
|
||||||
assertThatCode(() -> validator.validateHeaders(hostHeader("localhost:8080")))
|
|
||||||
.doesNotThrowAnyException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -343,11 +341,9 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
.allowedHosts(List.of("example.com:*", "test.com:3000"))
|
.allowedHosts(List.of("example.com:*", "test.com:3000"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> validator.validateHeaders(hostHeader("localhost:8080")))
|
assertThatCode(() -> validator.validate(hostAccessor("localhost:8080"))).doesNotThrowAnyException();
|
||||||
.doesNotThrowAnyException();
|
assertThatCode(() -> validator.validate(hostAccessor("example.com:9999"))).doesNotThrowAnyException();
|
||||||
assertThatCode(() -> validator.validateHeaders(hostHeader("example.com:9999")))
|
assertThatCode(() -> validator.validate(hostAccessor("test.com:3000"))).doesNotThrowAnyException();
|
||||||
.doesNotThrowAnyException();
|
|
||||||
assertThatCode(() -> validator.validateHeaders(hostHeader("test.com:3000"))).doesNotThrowAnyException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -365,60 +361,195 @@ class DefaultServerTransportSecurityValidatorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bothValid() {
|
void bothValid() {
|
||||||
var header = headers("http://localhost:8080", "localhost:8080");
|
var accessor = combinedAccessor("http://localhost:8080", "localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> combinedValidator.validateHeaders(header)).doesNotThrowAnyException();
|
assertThatCode(() -> combinedValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originValidHostInvalid() {
|
void originValidHostInvalid() {
|
||||||
var header = headers("http://localhost:8080", "malicious.example.com:8080");
|
var accessor = combinedAccessor("http://localhost:8080", "malicious.example.com:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> combinedValidator.validateHeaders(header)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> combinedValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originInvalidHostValid() {
|
void originInvalidHostValid() {
|
||||||
var header = headers("http://malicious.example.com:8080", "localhost:8080");
|
var accessor = combinedAccessor("http://malicious.example.com:8080", "localhost:8080");
|
||||||
|
|
||||||
assertThatThrownBy(() -> combinedValidator.validateHeaders(header)).isEqualTo(INVALID_ORIGIN);
|
assertThatThrownBy(() -> combinedValidator.validate(accessor)).isEqualTo(INVALID_ORIGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originMissingHostValid() {
|
void originMissingHostValid() {
|
||||||
// Origin missing is OK (same-origin request)
|
// Origin missing is OK (same-origin request)
|
||||||
var header = headers(null, "localhost:8080");
|
var accessor = combinedAccessor(null, "localhost:8080");
|
||||||
|
|
||||||
assertThatCode(() -> combinedValidator.validateHeaders(header)).doesNotThrowAnyException();
|
assertThatCode(() -> combinedValidator.validate(accessor)).doesNotThrowAnyException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void originValidHostMissing() {
|
void originValidHostMissing() {
|
||||||
// Host missing is NOT OK when allowedHosts is configured
|
// Host missing is NOT OK when allowedHosts is configured
|
||||||
var header = headers("http://localhost:8080", null);
|
var accessor = combinedAccessor("http://localhost:8080", null);
|
||||||
|
|
||||||
assertThatThrownBy(() -> combinedValidator.validateHeaders(header)).isEqualTo(INVALID_HOST);
|
assertThatThrownBy(() -> combinedValidator.validate(accessor)).isEqualTo(INVALID_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, List<String>> originHeader(String origin) {
|
@Nested
|
||||||
return Map.of("Origin", List.of(origin));
|
class DeprecatedMapBasedApi {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void originValidation() {
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("Origin", List.of("http://localhost:8080"));
|
||||||
|
|
||||||
|
assertThatCode(() -> validator.validateHeaders(headers)).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void originRejected() {
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("Origin", List.of("http://malicious.example.com"));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> validator.validateHeaders(headers)).isEqualTo(INVALID_ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void caseInsensitiveHeaderLookup() {
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("origin", List.of("http://localhost:8080"));
|
||||||
|
|
||||||
|
assertThatCode(() -> validator.validateHeaders(headers)).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hostValidation() {
|
||||||
|
DefaultServerTransportSecurityValidator hostValidator = DefaultServerTransportSecurityValidator.builder()
|
||||||
|
.allowedHost("localhost:8080")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("Host", List.of("localhost:8080"));
|
||||||
|
|
||||||
|
assertThatCode(() -> hostValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hostRejected() {
|
||||||
|
DefaultServerTransportSecurityValidator hostValidator = DefaultServerTransportSecurityValidator.builder()
|
||||||
|
.allowedHost("localhost:8080")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("Host", List.of("malicious.com:8080"));
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> hostValidator.validateHeaders(headers)).isEqualTo(INVALID_HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void emptyHeaders() {
|
||||||
|
assertThatCode(() -> validator.validateHeaders(new HashMap<>())).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void combinedOriginAndHost() {
|
||||||
|
DefaultServerTransportSecurityValidator combinedValidator = DefaultServerTransportSecurityValidator
|
||||||
|
.builder()
|
||||||
|
.allowedOrigin("http://localhost:*")
|
||||||
|
.allowedHost("localhost:*")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
headers.put("Origin", List.of("http://localhost:8080"));
|
||||||
|
headers.put("Host", List.of("localhost:8080"));
|
||||||
|
|
||||||
|
assertThatCode(() -> combinedValidator.validateHeaders(headers)).doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, List<String>> hostHeader(String host) {
|
@Nested
|
||||||
return Map.of("Host", List.of(host));
|
class ValidatorCompatibility {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noopAcceptsAll() {
|
||||||
|
assertThatCode(() -> ServerHttpHeaderValidator.NOOP.validate(emptyAccessor())).doesNotThrowAnyException();
|
||||||
|
assertThatCode(() -> ServerTransportSecurityValidator.NOOP.validateHeaders(new HashMap<>()))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void legacyValidatorAdaptsToHeaderAccessor() {
|
||||||
|
ServerTransportSecurityValidator legacyValidator = headers -> {
|
||||||
|
List<String> origins = headers.getOrDefault("Origin", List.of());
|
||||||
|
if (!origins.isEmpty() && origins.get(0).contains("evil")) {
|
||||||
|
throw new ServerTransportSecurityException(403, "Invalid Origin header");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ServerHttpHeaderValidator headerValidator = ServerTransportSecurityValidator
|
||||||
|
.toHttpHeaderValidator(legacyValidator);
|
||||||
|
|
||||||
|
assertThatCode(() -> headerValidator.validate(originAccessor("http://good.example.com")))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> headerValidator.validate(originAccessor("http://evil.example.com")))
|
||||||
|
.isEqualTo(INVALID_ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, List<String>> headers(String origin, String host) {
|
private static HeaderAccessor emptyAccessor() {
|
||||||
var map = new HashMap<String, List<String>>();
|
return headerAccessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HeaderAccessor headerAccessor(String... namesAndValues) {
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
|
for (int i = 0; i < namesAndValues.length; i += 2) {
|
||||||
|
headers.put(namesAndValues[i], List.of(namesAndValues[i + 1]));
|
||||||
|
}
|
||||||
|
return new HeaderAccessor() {
|
||||||
|
@Override
|
||||||
|
public List<String> getHeader(String name) {
|
||||||
|
return headers.getOrDefault(name, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaderNames() {
|
||||||
|
return List.copyOf(headers.keySet());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HeaderAccessor originAccessor(String origin) {
|
||||||
|
return headerAccessor("Origin", origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HeaderAccessor hostAccessor(String host) {
|
||||||
|
return headerAccessor("Host", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HeaderAccessor combinedAccessor(String origin, String host) {
|
||||||
|
Map<String, List<String>> headers = new HashMap<>();
|
||||||
if (origin != null) {
|
if (origin != null) {
|
||||||
map.put("Origin", List.of(origin));
|
headers.put("Origin", List.of(origin));
|
||||||
}
|
}
|
||||||
if (host != null) {
|
if (host != null) {
|
||||||
map.put("Host", List.of(host));
|
headers.put("Host", List.of(host));
|
||||||
}
|
}
|
||||||
return map;
|
return new HeaderAccessor() {
|
||||||
|
@Override
|
||||||
|
public List<String> getHeader(String name) {
|
||||||
|
return headers.getOrDefault(name, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getHeaderNames() {
|
||||||
|
return List.copyOf(headers.keySet());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ class ServerTransportSecurityIntegrationTests {
|
||||||
public Sse() {
|
public Sse() {
|
||||||
transport = HttpServletSseServerTransportProvider.builder()
|
transport = HttpServletSseServerTransportProvider.builder()
|
||||||
.messageEndpoint("/mcp/message")
|
.messageEndpoint("/mcp/message")
|
||||||
.securityValidator(DefaultServerTransportSecurityValidator.builder()
|
.httpHeaderValidator(DefaultServerTransportSecurityValidator.builder()
|
||||||
.allowedOrigin("http://localhost:*")
|
.allowedOrigin("http://localhost:*")
|
||||||
.allowedHost("localhost:*")
|
.allowedHost("localhost:*")
|
||||||
.build())
|
.build())
|
||||||
|
|
@ -242,7 +242,7 @@ class ServerTransportSecurityIntegrationTests {
|
||||||
|
|
||||||
public StreamableHttp() {
|
public StreamableHttp() {
|
||||||
transport = HttpServletStreamableServerTransportProvider.builder()
|
transport = HttpServletStreamableServerTransportProvider.builder()
|
||||||
.securityValidator(DefaultServerTransportSecurityValidator.builder()
|
.httpHeaderValidator(DefaultServerTransportSecurityValidator.builder()
|
||||||
.allowedOrigin("http://localhost:*")
|
.allowedOrigin("http://localhost:*")
|
||||||
.allowedHost("localhost:*")
|
.allowedHost("localhost:*")
|
||||||
.build())
|
.build())
|
||||||
|
|
@ -276,7 +276,7 @@ class ServerTransportSecurityIntegrationTests {
|
||||||
|
|
||||||
public Stateless() {
|
public Stateless() {
|
||||||
transport = HttpServletStatelessServerTransport.builder()
|
transport = HttpServletStatelessServerTransport.builder()
|
||||||
.securityValidator(DefaultServerTransportSecurityValidator.builder()
|
.httpHeaderValidator(DefaultServerTransportSecurityValidator.builder()
|
||||||
.allowedOrigin("http://localhost:*")
|
.allowedOrigin("http://localhost:*")
|
||||||
.allowedHost("localhost:*")
|
.allowedHost("localhost:*")
|
||||||
.build())
|
.build())
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue