Threadlocal 在升级到 JAVA 8 后出现 tomcat 错误行为
Threadlocal on tomcat misbehaviour after upgrading to JAVA 8
我使用本地线程来存储用户请求的特定功能(例如浏览器代理),它过去在 JAVA 7 上运行良好,但现在升级到 JAVA 8 在某些情况下我看到来自 android 浏览器的请求被处理为好像它来自 iOS 浏览器,即使它被正确检测为 android 浏览器但后来在处理请求时它被另一个线程本地值替换!我不确定这里缺少什么有人可以帮助我吗?我的环境设置 (before/after) 升级是:
- 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()
任何人都可以帮我找出这个问题的原因,或者更可靠的替代方法吗?
提前致谢:)
对于初学者,您的 SecurityManager
存在缺陷,您不应该获取实例,而应该使用 static
直接 get/set ThreadLocal
上的值。目前,当事情被加载到不同的 class 加载器中时,您可能 运行 会遇到问题,即未检测到单例。
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 安全工作的方式,例如 Springs 事务管理(使用静态方法和共享 ThreadLocal
)。
我使用本地线程来存储用户请求的特定功能(例如浏览器代理),它过去在 JAVA 7 上运行良好,但现在升级到 JAVA 8 在某些情况下我看到来自 android 浏览器的请求被处理为好像它来自 iOS 浏览器,即使它被正确检测为 android 浏览器但后来在处理请求时它被另一个线程本地值替换!我不确定这里缺少什么有人可以帮助我吗?我的环境设置 (before/after) 升级是:
- 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()
任何人都可以帮我找出这个问题的原因,或者更可靠的替代方法吗?
提前致谢:)
对于初学者,您的 SecurityManager
存在缺陷,您不应该获取实例,而应该使用 static
直接 get/set ThreadLocal
上的值。目前,当事情被加载到不同的 class 加载器中时,您可能 运行 会遇到问题,即未检测到单例。
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 安全工作的方式,例如 Springs 事务管理(使用静态方法和共享 ThreadLocal
)。