防盜鏈是指防止未授權網站通過直接引用你的資源鏈接(圖片、報表、視頻等)消耗你的帶寬與服務器資源。核心原理是檢查 HTTP 請求頭中的 Referer 字段,判斷請求來源是否在白名單內。
允許空 Referer 便於用戶直接訪問但防禦較弱;拒絕空 Referer 安全性高但可能誤傷正常用戶。業務背景:報表服務部署在 Tomcat,前端門戶系統負責登錄鑒權。需確保報表鏈接僅能從門戶頁面跳轉打開,防止用戶複製鏈接直接訪問或第三方網站盜用。
package com.fr.security;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
public class ReportAntiHotlinkFilter implements Filter {
// 允許的來源域名白名單(支援通配符前綴匹配)
private static final String[] ALLOWED_REFERER_PREFIXES = {
"https://portal.mycompany.com/",
"https://portal.mycompany.com/",
"https://localhost:8080/portal/"
};
// 非法訪問導向頁面
private static final String DENIED_URL = "/login.jsp?error=hotlink";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String referer = req.getHeader("Referer");
boolean allowed = false;
if (referer != null) {
for (String prefix : ALLOWED_REFERER_PREFIXES) {
if (referer.startsWith(prefix)) {
allowed = true;
break;
}
}
}
if (allowed) {
chain.doFilter(req, resp); // 合法來源,放行
} else {
// 非法來源:記錄審計日誌
String clientIp = req.getRemoteAddr();
String requestedUri = req.getRequestURI();
log.warn("防盜鏈攔截: IP={}, Referer={}, URI={}", clientIp, referer, requestedUri);
// 重定向到登錄/錯誤頁
resp.sendRedirect(req.getContextPath() + DENIED_URL);
}
}
}
@WebFilter)<filter>
<filter-name>ReportAntiHotlinkFilter</filter-name>
<filter-class>com.fr.security.ReportAntiHotlinkFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ReportAntiHotlinkFilter</filter-name>
<url-pattern>/ReportServer</url-pattern> <!-- FineReport 核心入口 -->
</filter-mapping>
| 場景 | Referer 值 | 預期結果 |
|---|---|---|
| 門戶頁面點擊報表鏈接 | https://portal.mycompany.com/report-list | ✅ 放行 |
| 第三方網站嵌入 iframe | https://evil.com/dashboard | ❌ 攔截跳轉登錄頁 |
| 用戶瀏覽器直接輸入報表 URL | null (空) | ❌ 攔截(或按策略放行) |
| 郵件/文檔中點擊鏈接 | 郵件客戶端域名 | ❌ 攔截(需加入白名單才放行) |