【问题标题】:Threadlocal on tomcat misbehaviour after upgrading to JAVA 8升级到 JAVA 8 后,tomcat 错误行为上的 Threadlocal
【发布时间】:2016-06-22 06:37:43
【问题描述】:

我使用本地线程来存储用户请求的特定功能(例如浏览器代理),它过去在 JAVA 7 上可以正常工作,但现在升级到 JAVA 8 后在某些情况下,我看到来自 android 浏览器的请求被处理得好像它的来自 iOS 浏览器,即使它被正确检测为 android 浏览器,但后来在处理请求时,它被另一个线程本地值替换!我不确定这里缺少什么有人可以帮助我吗?我的环境设置(之前/之后)升级是:

  • tomcat 8 之前和之后。
  • JAVA 从 7 升级到 8。
  • Spring 从 4.1.7 升级到 4.2.5
  • Spring 安全性从 3.2.3 升级到 4.03

我有一个看起来像这样的安全过滤器:

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private final IdentityService identityService;

public AuthenticationTokenProcessingFilter(IdentityService userService) {
    this.identityService = userService;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    SecurityManager.manager().clearManager();
    HttpServletRequest httpRequest = this.getAsHttpRequest(request);
    String agent = httpRequest.getHeader("User-Agent");
    SecurityManager.manager().setAgent(agent);

    ...

    chain.doFilter(request, response);
}

}

安全管理器如下所示:

import com.appseleon.platform.web.shared.CrossAppConstants;

public class SecurityManager {
private static SecurityManager manager;

private final ThreadLocal<String> agentContext = new ThreadLocal<String>();

private SecurityManager() {
    manager = this;
}

public void clearManager() {
    agentContext.set(null);
}

public static SecurityManager manager() {
    return manager;
}


public String getAgent() {
    String os = agentContext.get();
    if (os == null) {
        os = CrossAppConstants.DEFAULT_OS;
    }
    return os;
}

public void setAgent(String agent) {
    System.out.println("### os detected: " + agent);
    agentContext.set(agent);
}

}

最后,在设置代理后,在我的代码的各个区域,我调用 SecurityManager 来获取当前的用户代理:

SecurityManager.manager().getAgent()

谁能帮我找出这个问题的原因,或者找到更可靠的替代方法来解决这个问题?

提前致谢:)

【问题讨论】:

    标签: java spring tomcat thread-local


    【解决方案1】:

    首先,您的SecurityManager 有缺陷,您不应该获取实例,而只需使用static 直接获取/设置ThreadLocal 上的值。目前,当事物被加载到不同的类加载器中时,您可能会遇到问题,即没有检测到单例。

    public abstract class SecurityManager {
    
      private static final ThreadLocal<String> agentContext = new ThreadLocal<String>();
    
      private SecurityManager() { }
    
      public static void clearManager() {
          agentContext.set(null);
      }
    
    
      public static String getAgent() {
          String os = agentContext.get();
          if (os == null) {
              os = CrossAppConstants.DEFAULT_OS;
          }
          return os;
      }
    
      public static void setAgent(String agent) {
          System.out.println("### os detected: " + agent);
          agentContext.set(agent);
      }
    
    }
    

    然后直接在这个上调用get/set方法。

    在您的过滤器中,您应该将filterChain.doFilter 包装在try / finally 块中的finally 中,始终清除本地线程。

    try {
        chain.doFilter(request, response);
    } finally {
        SecurityManager.clearManager();
    }
    

    除了扩展 GenericFilterBean 之外,您可能还想扩展 OncePerRequestFilter 以确保此功能仅被调用一次(如果您的逻辑中有一些转发特别有用)并且它仅适用于 HttpServletRequest 类型的请求,为您节省一些代码。

    public class AuthenticationTokenProcessingFilter extends OncePerRequestFilter {
    ...
    
        @Override
        protected void doFilterInternal(HttpServletRequest req, HttpServletResponse ress, FilterChain chain) throws IOException, ServletException {
    
            String agent = req.getHeader("User-Agent");
            SecurityManager.setAgent(agent);
    
            ...
            try {
                chain.doFilter(request, response);
            } finally {
                SecurityManager.clearManager();
            }
        }
    }
    

    这也是 Spring Security 的工作方式和 Springs 事务管理的方式(使用静态方法和共享 ThreadLocal)。

    【讨论】:

    • 非常感谢,我会试一试并将结果发回!
    • 我花时间正确测试和监控它,这个解决方案运行良好 :) 谢谢!
    猜你喜欢
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    • 1970-01-01
    • 2015-05-06
    • 1970-01-01
    • 2020-05-25
    • 2017-01-26
    • 2019-10-17
    相关资源
    最近更新 更多