如何在自定义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
相关产品推荐
相关产品推荐

