应用Spring Security安全框架时,异步操作中安全上下文丢失问题的解决方案

问题描述

笔者最近在开发项目时遇到一个问题,项目应用Spring Security+JWT的安全框架,用户访问时前台会在Header中携带Authorization头,SS对其进行认证并将当前登录的主体信息存储到安全上下文,在当前用户访问的任何地方都可以通过安全上下文获取登录用户的信息(例如用户名、用户手机、用户id等等),但在请求中启用另外的线程执行之后操作的时候,笔者发现异步线程中的安全上下文全部丢失。代码如下:

Constants.EXECUTOR_SERVICE.execute(() -> {
    try {
        SecurityUser user = (SecurityUser) 
            SecurityContextHolder.getContext().getAuthentication();// 1
        // 业务动作
    } catch (Exception e) {
        // 异常信息记录
    } finally {
        // 清除操作
    }
});

1出,获取到的SecurityUser的属性全部为空,在接下来的业务操作中,获取到的登录人信息也全部为空。

解决方案

调试之后,发现安全上下文中的信息全部丢失,回想起之前研究SS时的文档中SecurityContextHolder, SecurityContext和Authentication对象的内容,发现SS的安全上下文默认是存储在ThreadLocal也就是线程本地的,启动其他线程执行的时候,当然就会丢失掉上下文信息。了解到问题所在,就可以修改代码了,笔者将上文中的代码进行如下修改:

SecurityContext securityContext = SecurityContextHolder.getContext();// 1
Constants.EXECUTOR_SERVICE.execute(() -> {
    try {
        SecurityContextHolder.setContext(securityContext);// 2
        SecurityUser user = (SecurityUser) 
            SecurityContextHolder.getContext().getAuthentication();
        // 业务动作
    } catch (Exception e) {
        // 异常信息记录
    } finally {
        // 清除操作
        SecurityContextHolder.clearContext();// 3
    }
});

其中:
1. 在调用者线程中获取安全上下文。
2. 将调用者中的安全上下文设置到当前业务子线程中的ThreadLocal中。
3. 线程执行业务动作完毕后,清除线程的安全上下文信息。

注意,第三步必不可少,不然会导致异步线程的安全上下文传播到线程池中,而如果该线程为线程池的核心线程,下次该线程执行时又没有设置安全上下文,则会获取到错误的登陆者信息(也就是这次设置的安全上下文信息)!

如上改动过后,就能在异步线程中获取安全上下文信息了^_^。

你可能感兴趣的:(Java,Spring,Spring,Security)