POST请求场景下OIDC授权丢失请求体问题及Quarkus调试
问题:OIDC授权流程丢失POST请求体(Quarkus 2.16.12环境)
场景描述
两个应用通过iframe集成,应用A向应用B发起POST表单请求:
- 用户已在应用B完成授权时,流程正常
- 用户未授权时,流程出现异常:
- 应用A构造发往应用B的POST请求
- 用户被重定向至微软OIDC身份提供商完成授权
- 授权完成后重定向回应用B的回调URL,再跳转至原请求页面
- 页面空白,原因是跳转采用GET请求,未携带原POST请求体
现有尝试及问题
尝试通过优先级1000的RouteFilter存储原POST请求体,但代码中始终无法获取到请求体,代码如下:
@Inject CurrentVertxRequest request; @RouteFilter(1000) void myFilter(RoutingContext rc) { Buffer body = request.getCurrent().request().body().result(); rc.response().headersEndHandler(new Handler<Void>() { @Override public void handle(Void event) { int statusCode = rc.response().getStatusCode(); if (statusCode == 401) { rc.redirect("back").result(); } if (rc.request().path().endsWith(LaunchHtml.PATH)) { if (statusCode == 302 || statusCode == 401 || statusCode == 403) { if (result != null) { String key = UUID.randomUUID().toString(); rc.response().addCookie(new CookieImpl(PREVIOUS_PAYLOAD, key)); PAYLOAD_STORE.put(key, body.toString()); } } } } }); rc.next(); }
问题核心:直接调用body().result()是同步阻塞方式,此时Vert.x尚未完成请求体的异步读取,导致获取到空值。
解决方案
1. 异步读取请求体
改用Vert.x的异步handler读取请求体,确保在获取到完整请求体后再执行后续逻辑。
2. 存储请求体并关联会话
将请求体存储到分布式缓存(如Quarkus的Infinispan)或会话中,通过Cookie标记关联的存储键。
3. 授权回调后恢复请求
在应用B的OIDC回调端点,检查是否存在存储的请求体,若存在则重新构造POST请求或填充到目标页面。
修正后的RouteFilter代码
import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.vertx.ext.web.RoutingContext; import io.vertx.core.buffer.Buffer; import io.vertx.ext.web.Cookie; import io.vertx.ext.web.CookieImpl; import java.util.UUID; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @ApplicationScoped public class PostPayloadFilter { private static final String PREVIOUS_PAYLOAD_COOKIE = "PREVIOUS_PAYLOAD"; // 生产环境建议用分布式缓存替代HashMap,避免集群环境数据不一致 private final java.util.Map<String, String> PAYLOAD_STORE = new java.util.concurrent.ConcurrentHashMap<>(); @Inject CurrentVertxRequest currentVertxRequest; @RouteFilter(1000) void capturePostPayload(RoutingContext rc) { // 仅处理POST请求 if (!"POST".equalsIgnoreCase(rc.request().method().name())) { rc.next(); return; } // 异步读取请求体 rc.request().bodyHandler(body -> { // 绑定响应结束后的处理逻辑 rc.response().headersEndHandler(v -> { int statusCode = rc.response().getStatusCode(); // 仅在需要重定向授权的场景下存储请求体 if ((statusCode == 401 || statusCode == 302) && rc.request().path().endsWith(LaunchHtml.PATH)) { if (body.length() > 0) { String payloadKey = UUID.randomUUID().toString(); PAYLOAD_STORE.put(payloadKey, body.toString()); // 设置Cookie,注意配置路径和有效期 Cookie cookie = CookieImpl.create(PREVIOUS_PAYLOAD_COOKIE, payloadKey) .setPath("/") .setMaxAge(300) // 5分钟有效期 .setHttpOnly(true); rc.response().addCookie(cookie); } } }); // 将读取到的请求体放回上下文,确保后续处理器能正常获取 rc.setBody(body); rc.next(); }); } }
补充说明
- 存储方案:生产环境建议使用Quarkus集成的Infinispan或Redis替代ConcurrentHashMap,避免集群部署时的节点间数据不一致问题。
- 回调恢复:在OIDC回调的处理方法中,读取Cookie中的
PREVIOUS_PAYLOAD键,从存储中取出请求体,然后重定向到目标页面时通过POST方式提交,或直接填充表单数据。 - Cookie安全:设置
HttpOnly和Secure属性(HTTPS环境下),避免XSS攻击风险。
内容的提问来源于stack exchange,提问作者Jozef Dropčo
相关产品推荐
相关产品推荐

