sa-token非Web上下文无法获取Request

0x02 非Web上下文无法获取Request

问题定位

在我们使用sa-token安全框架的时候,有时候会提示:SaTokenException:非Web上下文无法获取Request

错误截图:

img

在官方网站中,查看常见问题排查:

非Web上下文无法获取Request

img

错误追踪:

跟着源码可以看到如下代码:

public class SaTokenContextForSpring implements SaTokenContext {

    /**
     * 获取当前请求的 Request 包装对象
     */
    @Override
    public SaRequest getRequest() {
        return new SaRequestForServlet(SpringMVCUtil.getRequest());
    }

    // 省略......

}

// SpringMVCUtil.getRequest()
/**
 * 获取当前会话的 request 对象
 * @return request
 */
public static HttpServletRequest getRequest() {
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if(servletRequestAttributes == null) {
        throw new NotWebContextException("非 web 上下文无法获取 HttpServletRequest").setCode(SaSpringBootErrorCode.CODE_20101);
    }
    return servletRequestAttributes.getRequest();
}

代码梳理

梳理一下为什么会走到这里,以 StpUtil.getLoginId()为例

 public Object getLoginId() {

    // 1、先判断一下当前会话是否正在 [ 临时身份切换 ], 如果是则返回临时身份
    if(isSwitch()) {
        return getSwitchLoginId();
    }

     // 2、如果前端没有提交 token,则抛出异常: 未能读取到有效 token
     String tokenValue = getTokenValue(true);
     // 省略...
}

public String getTokenValue(boolean noPrefixThrowException){

    // 1、获取前端提交的 token (包含前缀值)
    String tokenValue = getTokenValueNotCut();

    // 省略...
}

// StpLogic.getTokenValueNotCut()
public String getTokenValueNotCut(){

    // 获取相应对象
    SaStorage storage = SaHolder.getStorage();
    SaRequest request = SaHolder.getRequest();
    
    //  省略...
}

最终都会 调用到 SaHolder.getStorage()

public static SaStorage getStorage() {
    return SaManager.getSaTokenContextOrSecond().getStorage();
}

// SaManager.getSaTokenContextOrSecond()
public static SaTokenContext getSaTokenContextOrSecond() {
    
    // s1. 一级Context可用时返回一级Context
    if(saTokenContext != null) {
        if(saTokenSecondContext == null || saTokenContext.isValid()) {
            // 因为 isValid 是一个耗时操作,所以此处假定:二级Context为null的情况下无需验证一级Context有效性 
            // 这样可以提升6倍左右的上下文获取速度 
            return saTokenContext;
        }
    }
    
    // s2. 一级Context不可用时判断二级Context是否可用 
    if(saTokenSecondContext != null && saTokenSecondContext.isValid()) {
        return saTokenSecondContext;
    }
    
    // s3. 都不行,就返回默认的 Context 
    return SaTokenContextDefaultImpl.defaultContext; 
}

查看 SaTokenContext的实现类,如果通过 sa-token-spring-boot-starter使用,会自动注入 SaTokenContextForSpring类作为 SaToken 的上下文管理,从而走到刚才报错的位置。

img

从源码中,我们可以看到,由于非Web上下文中无法直接获取HttpServletRequest对象,因此无法直接在子线程中使用SA-Token认证框架中的Web相关功能。

知道了问题原因所在,接下来,我们就来解决:


解决方案一:

提取SA-Token信息:从请求中提取SA-Token信息,并将其传递给子线程。您可以在主线程中解析请求参数或读取请求头,获取SA-Token,并将其传递给子线程。

使用线程局部变量:将SA-Token存储在线程局部变量中,以便子线程可以访问它。在主线程中,您可以将SA-Token存储在线程局部变量中,并在子线程中使用该变量来获取SA-Token。

自定义认证逻辑:在子线程中编写自定义的认证逻辑,以处理SA-Token的验证和其他相关操作。您可以使用现有的安全框架或库提供的功能,如加密算法、签名验证等,来进行认证。

以下是一个简单的示例代码,演示了如何在子线程中使用自定义认证逻辑来处理SA-Token:

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
  
public class SaTokenAuthentication {

    private static final ExecutorService executor = Executors.newSingleThreadExecutor();  
  
    public static void main(String[] args) {  
        // 模拟从请求中提取的SA-Token信息  
        String saToken = "your_sa_token";  
  
        // 提交给子线程执行的任务  
        executor.submit(() -> {  
            // 在子线程中进行自定义认证逻辑  
            boolean isAuthenticated = authenticate(saToken);  
  
            // 根据认证结果执行相应的操作  
            if (isAuthenticated) {  
                // 认证成功,执行其他操作...  
            } else {  
                // 认证失败,处理错误...  
            }  
        });  
    }  
  
    private static boolean authenticate(String saToken) {  
        // 在这里编写自定义的认证逻辑,以验证SA-Token的有效性和完整性等。  
        // 您可以使用现有的安全框架或库提供的功能来进行认证。  
        // 这里只是简单地模拟认证过程并返回结果。  
        return saToken.equals("valid_sa_token"; // 替换为您的验证逻辑  
    }  
}

解决方案二:

解决方法比较简单:

1、参数透传

修改接口,把需要在ThreadLocal中获取的参数透传到其它方法中。

解决方案:先获取你想要的值,再把这个值当做一个参数传递到这些方法中,而不是直接从方法内调用Sa-Token的APl。

2、重置上下文对象

在子线程中重置RequestContextHolder对象。

// 从主线程获取用户数据 放到局部变量中
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> testFuture = CompletableFuture.runAsync(() -> {
    // 把旧RequestAttributes放到新线程的RequestContextHolder中
    RequestContextHolder.setRequestAttributes(attributes);
    // 处理业务逻辑
    ......
    // 使用完成后记得清理 request 信息
    RequestContextHolder.resetRequestAttributes();
}

参考资料

https://zhuanlan.zhihu.com/p/672622496

相关推荐

  1. web鉴权access_token、AK/SK、session/cookie

    2024-04-09 03:46:02       8 阅读
  2. sa-token整合oauth2

    2024-04-09 03:46:02       10 阅读
  3. Vue3+springboot+sa-token

    2024-04-09 03:46:02       6 阅读
  4. GPT获取session token

    2024-04-09 03:46:02       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-09 03:46:02       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-09 03:46:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-09 03:46:02       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-09 03:46:02       20 阅读

热门阅读

  1. Python世界之错误和异常

    2024-04-09 03:46:02       14 阅读
  2. js str字符串和arr数组互相转换

    2024-04-09 03:46:02       16 阅读
  3. HBase高并发机制

    2024-04-09 03:46:02       19 阅读
  4. KamaCoder 52. 携带研究材料(第七期模拟笔试)

    2024-04-09 03:46:02       17 阅读
  5. 华为机试(03)

    2024-04-09 03:46:02       13 阅读
  6. I2C协议介绍

    2024-04-09 03:46:02       12 阅读
  7. 逐步学习Go-sync.Once(只执行一次)Exactly Once

    2024-04-09 03:46:02       10 阅读