Vertx+Pac4j实现SAML SP登出异常:登出后仍可访问受保护资源
基于Vertx+pac4j的SAML SP登出后仍可访问受保护资源问题排查与解决
问题描述
我用Vertx、pac4j及pac4j-vertx构建了一个基于SAML协议的Web应用,作为服务提供商(SP),身份提供商(IdP)采用simpleSAMLphp。
当前问题:用户在SP端执行登出操作后,无需重新进行SAML认证就能直接访问受保护资源/msg.html。通过Chrome的SAML-tracer插件验证,SAML与IdP的登出流程已正常完成,用户在IdP端已成功登出。
应用包含两个静态HTML页面:
- 登录页
login.html(路径/login,无需保护) - 受保护资源
msg.html(路径/msg.html,需认证后访问)
请问:
- 该问题的原因是什么?
- 如何解决才能让用户在SP端登出后无法访问受保护资源,必须重新完成SAML认证?
- Vertx与pac4j是否支持静态HTML页面?
相关代码
主Verticle代码
public class MainVerticle extends AbstractVerticle { @Override public void start(Promise<Void> startPromise) throws Exception { HttpServerOptions serverOptions = new HttpServerOptions().setMaxFormAttributeSize(65536); HttpServer server = vertx.createHttpServer(serverOptions); Router router = Router.router(vertx); LocalSessionStore vertxSessionStore = LocalSessionStore.create(vertx); SessionStore sessionStore = new VertxSessionStore(vertxSessionStore); Pac4jAuthProvider authProvider = new Pac4jAuthProvider(); SessionHandler sessionHandler = SessionHandler.create(vertxSessionStore); router.get("/login").handler(StaticHandler.create("src/main/resources/static/login.html")); SAML2Client saml2Client = this.saml2Client(); Config config = new Config("http://localhost:8888/callback",saml2Client); router.route().handler(sessionHandler); SecurityHandlerOptions options = new SecurityHandlerOptions().setClients("SAML2Client"); router.route("/msg.html").handler(new SecurityHandler(vertx, sessionStore, config, authProvider,options)); router.get("/msg.html").handler(rc ->{ rc.response().putHeader(CONTENT_TYPE, TEXT_HTML); rc.next(); }); router.get("/msg.html").handler(StaticHandler.create("src/main/resources/static/msg.html")); CallbackHandlerOptions callbackHandlerOptions = new CallbackHandlerOptions() .setDefaultUrl("/") .setMultiProfile(true); CallbackHandler callbackHandler = new CallbackHandler(vertx, sessionStore, config, callbackHandlerOptions); router.get("/callback").handler(callbackHandler); router.post("/callback").handler(BodyHandler.create().setMergeFormAttributes(true)); router.post("/callback").handler(callbackHandler); router.get("/logout").handler(SingleLogouthandler(vertx, config, sessionStore)); server.requestHandler(router).listen(8888, http -> { if (http.succeeded()) { startPromise.complete(); System.out.println("HTTP server started on port 8888"); } else { startPromise.fail(http.cause()); } }); } private SAML2Client saml2Client(){ SAML2Configuration cfg = new SAML2Configuration("samlConfig/samlKeystore.jks", "pac4j-demo-passwd", "pac4j-demo-passwd", "samlConfig/idp-metadata.xml"); cfg.setResponseBindingType(SAMLConstants.SAML2_POST_BINDING_URI); cfg.setServiceProviderEntityId("http://localhost:8888/callback?client_name=SAML2Client"); cfg.setServiceProviderMetadataPath(new File("target", "sp-metadata.xml").getAbsolutePath()); return new SAML2Client(cfg); } private Handler<RoutingContext> SingleLogouthandler(Vertx vertx, Config config, SessionStore sessionStore){ LogoutHandlerOptions logoutOptions = new LogoutHandlerOptions() .setCentralLogout(true) .setLocalLogout(true) .setDefaultUrl("http://localhost:8888/login"); return new LogoutHandler(vertx, sessionStore, logoutOptions, config); } }
登录页login.html代码
<!DOCTYPE html> <html> <head> <title>Login page</title> </head> <body> <h2>Log in</h2> <a href="/msg.html">Login using SAML</a><br/> </body> </html>
受保护资源msg.html代码
<!DOCTYPE html> <html> <head> <title>Login Successful</title> </head><body> <h1>Login Successful</h1> <a href="/logout">local logout</a> </body> </html>
问题分析与解决方案
问题原因
- SP端会话未彻底清除:虽然IdP端登出成功,但SP本地会话中的pac4j认证信息可能未被正确销毁。即便配置了
setLocalLogout(true),也可能因会话存储交互问题,导致SecurityHandler误判用户仍处于已认证状态。 - 路由顺序或缓存问题:浏览器可能缓存了
/msg.html页面,无需请求服务器即可直接显示;或者StaticHandler的执行优先级高于SecurityHandler,导致请求未经过认证校验就返回了页面内容。
解决方法
强制销毁SP本地会话
在登出处理器后添加手动销毁会话的逻辑,确保SP端认证状态完全清除:router.get("/logout") .handler(SingleLogouthandler(vertx, config, sessionStore)) .handler(rc -> { rc.session().destroy(); // 手动销毁当前会话 rc.response().redirect("http://localhost:8888/login"); });修正路由执行顺序
确保SecurityHandler是/msg.html请求的第一个处理器,避免StaticHandler提前返回内容:router.route("/msg.html") .handler(new SecurityHandler(vertx, sessionStore, config, authProvider, options)) .handler(rc -> { rc.response().putHeader(CONTENT_TYPE, TEXT_HTML); rc.next(); }) .handler(StaticHandler.create("src/main/resources/static/msg.html"));禁用静态资源缓存
配置StaticHandler关闭浏览器缓存,防止受保护页面被缓存:StaticHandler.create("src/main/resources/static/msg.html") .setCachingEnabled(false) .setCacheTime(0);
Vertx与pac4j对静态HTML页面的支持
Vertx原生通过StaticHandler支持静态HTML页面,pac4j-vertx的SecurityHandler可以与StaticHandler完美配合,只要保证认证校验处理器先于静态资源处理器执行,就能实现对静态HTML页面的访问控制,两者完全兼容。
内容的提问来源于stack exchange,提问作者Jorge Domingo
相关产品推荐
相关产品推荐

