dromara / Sa-Token

一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式Session会话、微服务网关鉴权、单点登录、OAuth2.0
https://sa-token.cc
Apache License 2.0
16.48k stars 2.6k forks source link

未能获取有效的上下文 #675

Open Joker-Q4 opened 2 months ago

Joker-Q4 commented 2 months ago

使用版本:

1.38.0

涉及的功能模块:

gateway

测试步骤:

public static Box getBoxNotNull() { Box box = (Box)boxThreadLocal.get(); if (box == null) { throw (new SaTokenContextException("未能获取有效的上下文")).setCode(10002); } else { return box; } }

+ 我的理解是:
WebClient.Builder请求后切换了线程,导致SaTokenContextForThreadLocalStorage的InheritableThreadLocal拿不到数据,但是WebClient是webflux的最佳请求方案

builder.build() .post() .uri(uriBuilder -> { URI build = uriBuilder .path("/serviceValidate") .queryParam("ticket", ticket) .queryParam("service", service) .queryParam("pgtUrl", pgtUrl) .queryParam("renew", renew) .build(); log.info("url: {}", build); return build; }) .retrieve() .bodyToMono(String.class)

            .subscribeOn(Schedulers.fromExecutorService(createExecutorService))

            .publishOn(Schedulers.parallel())
            .retry(3)
            .doOnError(throwable -> {
                ExceptionUtil.printStackTrace(throwable);
            });


有没有办法在不添加ExecutorService的情况下解决这个问题?
CKzcb commented 2 months ago

可以参考这个文章解决:https://blog.csdn.net/weixin_40116478/article/details/141557676?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22141557676%22%2C%22source%22%3A%22weixin_40116478%22%7D

Joker-Q4 commented 2 months ago

可以参考这个文章解决:https://blog.csdn.net/weixin_40116478/article/details/141557676?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22141557676%22%2C%22source%22%3A%22weixin_40116478%22%7D

不是这个问题,是webclient和框架冲突了

Joker-Q4 commented 2 months ago
builder.build()
                .post()
                .uri(uriBuilder -> {
                    URI build = uriBuilder
                            .path("/serviceValidate")
                            .queryParam("ticket", ticket)
                            .queryParam("service", service)
                            .queryParam("pgtUrl", pgtUrl)
                            .queryParam("renew", renew)
                            .build();
                    log.info("url: {}", build);
                    return build;
                })
                .retrieve()
                .bodyToMono(String.class)
                .retry(3)
                .doOnError(throwable -> {
                    ExceptionUtil.printStackTrace(throwable);
                });

添加了也不行

zhuyeHe commented 2 months ago

我也遇到了同样的问题,使用的是spring-boot-starter-webflux 和 sa-token-reactor-spring-boot3-starter 1.39.0

    /**
     * 在当前线程的 SaRequest 包装对象
     * 
     * @return /
     */
    public static SaRequest getRequest() {
        return getBoxNotNull().getRequest();
    }

    /**
     * 在当前线程的 SaResponse 包装对象
     * 
     * @return /
     */
    public static SaResponse getResponse() {
        return getBoxNotNull().getResponse();
    }

    /**
     * 在当前线程的 SaStorage 存储器包装对象
     * 
     * @return /
     */
    public static SaStorage getStorage() {
        return getBoxNotNull().getStorage();
    }

我发现http请求中进行StpUtil.login()时,会调用两次getRequest,调用一次getStorage,其中在getStorage()时,报错:未能获取有效的上下文

分析源码发现box的初始化发生在此处:

SaReactorSyncHolder:

public static void setContext(ServerWebExchange exchange) {
SaRequest request = new SaRequestForReactor(exchange.getRequest());
SaResponse response = new SaResponseForReactor(exchange.getResponse());
SaStorage storage = new SaStorageForReactor(exchange);
SaTokenContextForThreadLocalStorage.setBox(request, response, storage);
}

SaTokenContextForThreadLocalStorage中:


/**
* 基于 ThreadLocal 的 [ Box 存储器 ]
*/
public static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<>();
/**
 * 初始化当前线程的 [ Box 存储器 ]
 * @param request {@link SaRequest}
 * @param response {@link SaResponse}
 * @param storage {@link SaStorage}
 */
public static void setBox(SaRequest request, SaResponse response, SaStorage storage) {
    Box bok = new Box(request, response, storage);
    boxThreadLocal.set(bok);
}


两次getRequest使用的是同一个box,而getStorage重新初始化了一个box,也就是重新调用了setContext方法(setContext时,box是初始化成功的了,但是在getBoxNotNull时,拿到的为null)

执行过程中其实SaSession已经创建好,并且在debug时也可以通过打印的token拿出来,但是在SaStorage storage = SaHolder.getStorage();时发生错误,我发获取storage

我不知道是否是因为getStorage前调用了setContext重新初始化box导致的
zhuyeHe commented 2 months ago

以下方法执行失败报错:未能获取有效的上下文

public Mono<Result> login(AccountDTO accountDTO) {
        return Mono.just(accountDTO)
                .flatMap(dto -> {
                    if (StrUtil.isNotBlank(dto.getMail())) {
                        return getByMail(dto.getMail());
                    } else if (StrUtil.isNotBlank(dto.getUsername())) {
                        return getByUsername(dto.getUsername());
                    } else {
                        return Mono.error(new ServiceException(CommonResultType.PARAMETER_ERROR));
                    }
                })
                .map(account -> {
                    StpUtil.login(account.getAccountId());
                    return Result.success(StpUtil.getSessionByLoginId(account.getAccountId()));
                });
    }

强制转化为阻塞式之后执行成功

public Mono<Result> login(AccountDTO accountDTO) {
        return Mono.fromCallable(() -> {
                    if (StrUtil.isBlank(accountDTO.getPassword()) || StrUtil.isBlank(accountDTO.getMail())) {
                        return Result.fail(CommonResultType.PARAMETER_ERROR);
                    }
                    Account loginAccount = getByMail(accountDTO.getMail()).block();
                    StpUtil.login(loginAccount.getAccountId());
                    //登陆成功,返回token
                    return Result.success(StpUtil.getTokenInfo());
                })
                .subscribeOn(Schedulers.boundedElastic())  // 切换到 boundedElastic 线程池
                ;
    }

两种写法,在执行getRequest和getStorage时都是不同的线程,但是不太清除为什么强制阻塞后可以执行成功

zhangruiyu commented 1 month ago

同样问题

ahzvenol commented 4 weeks ago

spring-boot-starter-webflux,sa-token-reactor-spring-boot3-starter +1

zhangruiyu commented 1 week ago

https://github.com/dromara/Sa-Token/issues/445

zhangruiyu commented 1 week ago

https://github.com/dromara/Sa-Token/issues/506 参考着2个应该可以解决