SpringSecurity过滤器WebAsyncManagerIntegrationFilter

原文地址

我们从ThreadLocal讲起,ThreadLocal可以理解为一个与当前线程绑定的Map, key是当前线程,value是要存储的object。当我们在同一个线程中,可以通过get()方法获取到value。如果在A线程set(object),然后在B线程中调用get(),由于线程已切换,key是A,拿B自然取不出key为A的value。

对于一个SSO系统,我们常常把当前登录的用户相关信息放到ThreadLocal中,避免每次使用的时候都去调RPC接口或者DB接口去查询。Spring Security的SecurityContextHolder就是通过ThreadLocal实现的。

WebAsyncManager,Spring文档有这么一句话:The central class for managing asynchronous request processing, mainly intended as an SPI and not typically used directly by application classes. 它主要用于异步请求,笔者发现这个东西在spring mvc请求分发处理中用到很多,时间有限,笔者也没有深入的了解,我们的主要关注点是WebAsyncManagerIntegrationFilter。

public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {

	private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
			
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		
		SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
		
		if (securityProcessingInterceptor == null) {
			asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,new SecurityContextCallableProcessingInterceptor());
		}

		filterChain.doFilter(request, response);
	}
}

SecurityContextCallableProcessingInterceptor,这个拦截器的关键作用,就是执行前后存储和清空SecurityContext,以便在WebAsyncTask异步调用中获取到SecurityContext,核心代码如下:

public final class SecurityContextCallableProcessingInterceptor extends
		CallableProcessingInterceptorAdapter {
	
	private volatile SecurityContext securityContext;


	public SecurityContextCallableProcessingInterceptor() {
	}

	public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) {
		Assert.notNull(securityContext, "securityContext cannot be null");
		setSecurityContext(securityContext);
	}

	@Override
	public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
		if (securityContext == null) {
			setSecurityContext(SecurityContextHolder.getContext());
		}
	}

	@Override
	public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
		SecurityContextHolder.setContext(securityContext);
	}

	@Override
	public <T> void postProcess(NativeWebRequest request, Callable<T> task,
			Object concurrentResult) {
		SecurityContextHolder.clearContext();
	}

	private void setSecurityContext(SecurityContext securityContext) {
		this.securityContext = securityContext;
	}
}

笔者做了一个实践,分别一个Runnable和WebAsyncTask中尝试从SecurityContextHolder获取当前登录用户,AuthHolder只是对SecurityContextHolder进行封装,本质上还是调用的SecurityContextHolder.get()获取线程上下文信息,代码如下:

@GetMapping("/run_async_task")
    public Map<String,String> getContextByRunnable() throws Exception {
        System.out.println("main线程[{"+Thread.currentThread().getName()+"}]开始");
        Map<String,String> map = new HashMap<>();
        CountDownLatch latch = new CountDownLatch(1);
        Thread async = new Thread(() -> {
            try {
                System.out.println("Runnable异步线程[{"+Thread.currentThread().getName()+"}], 开始执行异步任务");
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if(null==authentication){
                    System.out.println("获取认证信息失败");
                }else {
                    String name = authentication.getName();
                    map.put("name",name);
                }
                
                System.out.println("Runnable异步线程[{"+Thread.currentThread().getName()+"}], 任务执行完成,当前用户是: "+map);
            } finally {
                latch.countDown();
            }
        });
        async.start();
        latch.await();
        System.out.println("main线程[{"+Thread.currentThread().getName()+"}]结束");
        return map;
    }
}

main线程[{http-nio-8081-exec-4}]开始
Runnable异步线程[{Thread-11}], 开始执行异步任务
获取认证信息失败
Runnable异步线程[{Thread-11}], 任务执行完成,当前用户是: {}
main线程[{http-nio-8081-exec-4}]结束

@GetMapping("/web_async_task")
public WebAsyncTask getContextByWebAsyncTask() {
       System.out.println("main线程[{"+Thread.currentThread().getName()+"}]开始");
       WebAsyncTask<String> task = new WebAsyncTask<>(300L, () -> {
           System.out.println("WebAsyncTask异步线程[{"+Thread.currentThread().getName()+"}], 开始执行异步任务");
           String name = SecurityContextHolder.getContext().getAuthentication().getName();

           System.out.println("WebAsyncTask异步线程[{"+Thread.currentThread().getName()+"}], 任务执行完成,当前用户是: "+name);
           return name;
       });
       System.out.println("main线程[{"+Thread.currentThread().getName()+"}]结束");
       return task;
}

main线程[{http-nio-8081-exec-6}]开始
main线程[{http-nio-8081-exec-6}]结束
WebAsyncTask异步线程[{task-1}], 开始执行异步任务
WebAsyncTask异步线程[{task-1}], 任务执行完成,当前用户是: user

可以看出,WebAsyncTask从SecurityContextHolder获取到了身份认证信息,但Runnable却不可以。WebAsyncTask之所以能够在异步线程中从SecurityContextHolder中获取上下文信息,与WebAsyncManagerIntegrationFilter的作用密不可分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值