You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在自定义Realm中获取客户端IP地址?

如何在自定义Realm中获取客户端IP地址?

我之前也碰到过一模一样的问题——Tomcat的Realm组件确实没法直接拿到HttpServletRequest对象,不过用ThreadLocal来传递请求上下文是个很实用的解决方案,刚好能完美适配你现在的场景,给你一步步拆解:

步骤1:写一个ThreadLocal工具类存请求对象

这个类的作用就是把当前请求的HttpServletRequest绑定到处理请求的线程上,这样Realm就能从同一个线程里拿到它:

public class RequestHolder {
    private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();

    // 存入请求对象
    public static void setRequest(HttpServletRequest request) {
        requestHolder.set(request);
    }

    // 获取请求对象
    public static HttpServletRequest getRequest() {
        return requestHolder.get();
    }

    // 清理线程绑定,避免内存泄漏
    public static void clear() {
        requestHolder.remove();
    }
}

步骤2:写一个Filter提前捕获请求

这个Filter要在Tomcat的认证逻辑之前执行,把请求对象放到ThreadLocal里,请求结束后记得清理:

public class RequestCaptureFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest) {
            RequestHolder.setRequest((HttpServletRequest) servletRequest);
        }
        try {
            // 继续执行后续过滤链(包括Tomcat的认证逻辑)
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            // 必须清理,不然线程池复用会导致数据错乱或内存泄漏
            RequestHolder.clear();
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}

步骤3:在web.xml中配置Filter

一定要把这个Filter映射到/j_security_check路径,并且确保它在Tomcat的认证Filter之前执行:

<filter>
    <filter-name>RequestCaptureFilter</filter-name>
    <filter-class>com.yourpackage.RequestCaptureFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>RequestCaptureFilter</filter-name>
    <url-pattern>/j_security_check</url-pattern>
</filter-mapping>

步骤4:在自定义Realm中获取IP地址

现在你就能在authenticate方法里拿到请求对象,进而获取客户端IP了:

@Override
public Principal authenticate(String username, String credentials) {
    System.out.println("AUTHENTICATE in CustomLoginRealm");
    System.out.println("Thread in Realm: " + Thread.currentThread().getId());

    Principal principal = super.authenticate(username, credentials);

    if (principal == null) {
        HttpServletRequest request = RequestHolder.getRequest();
        String clientIp = "unknown";
        if (request != null) {
            // 基础获取方式:拿到直接连接的客户端IP
            clientIp = request.getRemoteAddr();
            
            // 如果你的应用在反向代理(如Nginx)后面,需要取代理传递的头
            // String forwardedIp = request.getHeader("X-Forwarded-For");
            // if (forwardedIp != null && !forwardedIp.isEmpty()) {
            //     // 注意:X-Forwarded-For可能有多个IP,取第一个非未知的
            //     clientIp = forwardedIp.split(",")[0].trim();
            // }
        }
        // 把IP传入日志方法
        logFailedLoginAttempt(username, clientIp);
    }
    return principal;
}

注意事项

  • 务必在Filter的finally块里清理ThreadLocal,因为Tomcat用线程池复用线程,不清理会导致后续请求拿到错误的请求对象,甚至内存泄漏。
  • 如果应用部署在反向代理后面,不要直接用getRemoteAddr,要从X-Forwarded-For或X-Real-IP头取,但要确保代理服务器已经配置了正确的头,并且限制只有信任的代理才能传递这些头,防止伪造IP。

备注:内容来源于stack exchange,提问作者Marios

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 15:14:39