Spring Boot 4.1 InetAddressFilter: Built-in SSRF Protection for HTTP Clients

Spring Boot 4.1 introduces InetAddressFilter to block internal network access at the HTTP client level, replacing scattered string checks with a centralized allowlist approach. The article demonstrates configuring RestClient.Builder for external-only access, adding URI validation, disabling auto-redirects, setting timeouts, limiting response size, and enforcing secure patterns via tests and code reviews.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Spring Boot 4.1 InetAddressFilter: Built-in SSRF Protection for HTTP Clients

SSRF Risk in a Simple Link Preview

A link preview feature fetches a user-supplied URL, extracts title, description, and image, and renders a share card. The naive implementation uses Spring's RestClient to GET the URL directly:

@RestController
@RequestMapping("/api/link")
public class LinkPreviewController {
    private final RestClient restClient;
    public LinkPreviewController(RestClient.Builder builder) {
        this.restClient = builder.build();
    }
    @GetMapping("/preview")
    public String preview(@RequestParam String url) {
        return restClient.get()
                .uri(url)
                .retrieve()
                .body(String.class);
    }
}

Because the server controls the outbound request, an attacker can supply http://127.0.0.1:8080/xxx or http://10.0.0.10/xxx to reach internal services, databases, Redis, or admin interfaces — a classic Server-Side Request Forgery (SSRF).

Why Ad-hoc String Checks Fail

Early fixes block localhost or 127.0.0.1 via string matching, but attackers bypass with 127.0.0.1, IPv6 ::1, private CIDR blocks ( 10.x.x.x, 172.16-31.x.x, 192.168.x.x), or domain names that resolve to internal IPs (e.g., internal.example.test → 192.168.1.20). A stronger approach resolves the host and checks InetAddress.isLoopbackAddress() or isSiteLocalAddress(), yet this logic ends up duplicated across every feature that calls external URLs (link preview, image download, webhook test, file sync, remote markdown import, third-party API, avatar fetch, callback verification).

Spring Boot 4.1: Centralized InetAddressFilter

Spring Boot 4.1 adds InetAddressFilter to the auto-configured HTTP client stack. It defines allowed addresses, not blocked ones. The simplest secure configuration for a public-only client:

@Configuration(proxyBeanMethods = false)
public class HttpClientSecurityConfig {
    @Bean
    public InetAddressFilter httpClientInetAddressFilter() {
        return InetAddressFilter.externalAddresses();
    }
}
externalAddresses()

permits only external (non-loopback, non-private) destinations. The existing RestClient code remains unchanged provided it uses the Spring-boot-managed RestClient.Builder:

@Service
public class RemotePageService {
    private final RestClient restClient;
    public RemotePageService(RestClient.Builder builder) {
        this.restClient = builder.build();
    }
    public String download(String url) {
        return restClient.get().uri(url).retrieve().body(String.class);
    }
}

If developers instantiate clients via RestClient.create() or RestClient.builder().build(), the filter is bypassed. The author replaces all such occurrences with injected RestClient.Builder and adds a code-review rule: "Business code must not create HTTP clients directly; obtain the Builder from the Spring container."

Two-Layer Defense: URI Validation + Network Filter

Layer 1 (business): validate URI syntax and scheme — HTTP/HTTPS only, host present, no user-info.

@Component
public class ExternalUrlValidator {
    public URI validate(String value) {
        URI uri;
        try { uri = URI.create(value); }
        catch (IllegalArgumentException e) { throw new InvalidUrlException("URL 格式不正确"); }
        String scheme = uri.getScheme();
        if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))
            throw new InvalidUrlException("只允许 HTTP 和 HTTPS");
        if (uri.getHost() == null || uri.getHost().isBlank())
            throw new InvalidUrlException("URL 缺少有效域名");
        if (uri.getUserInfo() != null)
            throw new InvalidUrlException("URL 不允许包含用户信息");
        return uri;
    }
}

Layer 2 (network): InetAddressFilter checks the resolved IP at connection time. This separation keeps business logic clean and ensures the filter cannot be circumvented by DNS tricks.

Redirect Handling

Auto-following redirects (301/302/307/308) can redirect a safe public URL to an internal one. The author disables automatic redirects via configuration:

spring:
  http:
    clients:
      connect-timeout: 2s
      read-timeout: 3s
      redirects: dont-follow

If redirects are required, implement a custom follower that re-validates each Location header against the same URI and network policies, limiting hops (e.g., max 3) and allowing only http→http, http→https, https→https.

Timeouts and Response Size Limits

Unbounded reads let an attacker stall threads or exhaust memory. The final downloader uses exchange() to enforce:

Connect timeout 2s, read timeout 3s (via config)

Max body 512 KB ( input.readNBytes(MAX_BODY_SIZE + 1))

Content-Type must be HTML

Non-2xx status throws exception

public String download(String url) {
    URI uri = validator.validate(url);
    return restClient.get()
            .uri(uri)
            .exchange((request, response) -> {
                if (!response.getStatusCode().is2xxSuccessful())
                    throw new RemotePageException("远程页面返回:" + response.getStatusCode());
                MediaType contentType = response.getHeaders().getContentType();
                if (contentType != null && !MediaType.TEXT_HTML.isCompatibleWith(contentType))
                    throw new RemotePageException("远程内容不是 HTML");
                try (InputStream input = response.getBody()) {
                    byte[] bytes = input.readNBytes(MAX_BODY_SIZE + 1);
                    if (bytes.length > MAX_BODY_SIZE)
                        throw new RemotePageException("远程页面过大");
                    return new String(bytes, StandardCharsets.UTF_8);
                }
            });
}

Error Handling Without Leaking Internals

A global exception handler catches FilteredHostException (thrown by the filter) and returns a generic 403 with title "目标地址不可访问" and detail "该 URL 指向禁止访问的网络地址", avoiding disclosure of the resolved internal IP.

Testing and Governance

Unit tests verify the filter blocks loopback and private addresses:

class InetAddressFilterTest {
    private final InetAddressFilter filter = InetAddressFilter.externalAddresses();
    @Test void shouldBlockLoopback() throws Exception {
        InetAddress address = InetAddress.getByName("127.0.0.1");
        assertThat(filter.matches(address)).isFalse();
    }
    @Test void shouldBlockPrivateAddress() throws Exception {
        InetAddress address = InetAddress.getByName("192.168.1.10");
        assertThat(filter.matches(address)).isFalse();
    }
}

More importantly, automated grep checks prevent regression:

grep -R "RestClient.create" src/main/java
grep -R "RestClient.builder" src/main/java

Any direct client creation fails the review, analogous to forbidding DriverManager.getConnection() in favor of a managed DataSource.

Conclusion

Spring Boot 4.1's InetAddressFilter fills a real gap: developers have long parameterized SQL, file paths, and deserialization, but treated outbound HTTP as harmless. Any URL originating from user input, Excel uploads, webhook configs, third-party callbacks, editable DB fields, or open-platform parameters must be treated as untrusted. The recommended order is now: when creating the RestClient, define allowed address ranges, timeouts, redirect policy, response size limits, and accepted protocols — before writing any business logic. Outbound HTTP is a capability that deserves the same rigor as inbound request handling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaRestClientBackend SecuritySSRFSpring Boot 4.1InetAddressFilterHTTP Client Security
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.