Backend Development 8 min read

Implementing Image Anti-Hotlinking in Spring Boot with a Configurable Interceptor

This article explains how to prevent image hotlinking in a Spring Boot application by creating a simple interceptor with hard‑coded settings and then extending it to a flexible, configuration‑driven solution using application.yml and a properties‑mapping class.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing Image Anti-Hotlinking in Spring Boot with a Configurable Interceptor

For security and performance reasons, you may want to restrict image access so that only requests originating from allowed domains can retrieve the image, preventing hotlinking and unnecessary bandwidth consumption.

import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ImageProtectionInterceptor implements HandlerInterceptor { private static final String ALLOWED_DOMAIN = "baidudu.com"; // allowed domain @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestUrl = request.getRequestURL().toString(); if (requestUrl.endsWith(".jpg") || requestUrl.endsWith(".png") || requestUrl.endsWith(".jpeg")) { String referer = request.getHeader("Referer"); if (referer != null && referer.contains(ALLOWED_DOMAIN)) { return true; // allowed, let the request pass } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; // block the request } } return true; // non‑image resources are allowed } }

Register the interceptor so that it intercepts all incoming requests.

import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ImageProtectionInterceptor()) .addPathPatterns("/**"); // intercept all paths } }

To make the solution configurable, add a section in application.yml :

# Image anti‑hotlink configuration img-protect: # enable or disable the feature enabled: true # allow direct browser access allowBrowser: false # comma‑separated whitelist of allowed referer domains (empty means block all) allowReferer: baidudu.com

Create a POJO that maps the YAML properties.

import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties("img-protect") public class ImgProtectConfig { private boolean enabled; private boolean allowBrowser; private String allowReferer; public boolean getEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public boolean getAllowBrowser() { return allowBrowser; } public void setAllowBrowser(boolean allowBrowser) { this.allowBrowser = allowBrowser; } public String getAllowReferer() { return allowReferer; } public void setAllowReferer(String allowReferer) { this.allowReferer = allowReferer; } }

Rewrite the interceptor to use the configuration bean.

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @Component public class ImageProtectionInterceptor implements HandlerInterceptor { @Autowired private ImgProtectConfig imgProtectConfig; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!imgProtectConfig.getEnabled()) { return true; // feature disabled } String requestUrl = request.getRequestURL().toString(); if (requestUrl.endsWith(".jpg") || requestUrl.endsWith(".png") || requestUrl.endsWith(".jpeg")) { String referer = request.getHeader("Referer"); if (referer == null && imgProtectConfig.getAllowBrowser()) { return true; // direct browser access allowed } else if (referer != null && isAllowedDomain(referer)) { return true; // referer matches whitelist } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; // block } } return true; // non‑image resources are allowed } private boolean isAllowedDomain(String referer) { String allowedReferers = imgProtectConfig.getAllowReferer(); if (allowedReferers != null && !allowedReferers.trim().isEmpty()) { Set allowedDomains = new HashSet<>(Arrays.asList(allowedReferers.split(","))); for (String domain : allowedDomains) { if (referer.contains(domain.trim())) { return true; } } } return false; } }

Register the bean‑based interceptor (note the use of @Autowired instead of new ).

import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.beans.factory.annotation.Autowired; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private ImageProtectionInterceptor imageProtectionInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(imageProtectionInterceptor) .addPathPatterns("/**"); } }

Finally, the article notes that while this interceptor blocks most hotlinking attempts, it cannot guarantee absolute security because referer headers can be forged, data‑URI images can bypass checks, and legitimate users behind privacy‑focused browsers may be blocked.

backendJavaConfigurationSpring BootInterceptoranti-hotlinkingImage Protection
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login 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.