如何解决Session认证前端调用JWT保护API的Token过期问题?
解决Vert.x + Keycloak跨应用认证的令牌刷新问题
核心问题原因
应用1的OAuth2Auth Session默认不会自动刷新访问令牌,当初始令牌过期后,会话中存储的principal仍保留旧令牌,导致前端获取的令牌无效,调用API时返回401。
调整方案
1. 配置OAuth2Auth自动处理令牌刷新
初始化OAuth2Auth时,开启刷新令牌支持并添加必要权限,让Vert.x能自动管理令牌生命周期:
OAuth2Auth oauth2 = OAuth2Auth.create(vertx, OAuth2FlowType.AUTH_CODE, new OAuth2Options() .setClientId("your-client-id") .setClientSecret("your-client-secret") .setSite("https://your-keycloak-domain/auth/realms/your-realm") .setRefreshToken(true) // 启用刷新令牌支持 .setTokenPath("/protocol/openid-connect/token") .setAuthorizationPath("/protocol/openid-connect/auth") .setScope("openid offline_access")); // 添加offline_access获取持久化刷新令牌
同时确保会话存储能保留刷新令牌,避免应用重启后丢失。
2. 修改应用1的令牌端点逻辑,主动检查并刷新令牌
在提供访问令牌的端点中,先判断令牌是否过期或即将过期,触发刷新并更新会话:
router.get("/api/token").handler(ctx -> { User user = ctx.user(); if (user == null) { ctx.response().setStatusCode(401).end("Unauthorized"); return; } JsonObject principal = user.principal(); String accessToken = principal.getString("access_token"); JsonObject decodedJwt = JWT.decode(accessToken); long expiresAt = decodedJwt.getLong("exp") * 1000; // 提前30秒触发刷新,避免令牌刚好用过期 if (System.currentTimeMillis() >= expiresAt - 30000) { OAuth2Auth oauth2 = ctx.vertx().getLocal("oauth2-auth"); oauth2.refresh(user) .onSuccess(refreshedUser -> { ctx.setUser(refreshedUser); ctx.response() .putHeader("Content-Type", "application/json") .end(new JsonObject().put("access_token", refreshedUser.principal().getString("access_token")).encode()); }) .onFailure(err -> { // 刷新失败,引导用户重新登录 ctx.response().setStatusCode(401).end("Token refresh failed, please re-login"); }); } else { // 令牌有效,直接返回 ctx.response() .putHeader("Content-Type", "application/json") .end(new JsonObject().put("access_token", accessToken).encode()); } });
3. 前端配合优化令牌生命周期管理
前端存储令牌时同步保存过期时间戳:
- 每次调用API前,检查当前时间是否接近过期(比如提前1分钟)
- 若即将过期,先调用应用1的令牌端点获取新令牌,再发起API请求
- 若刷新失败,直接引导用户重新登录
4. 采用持久化会话存储
替换默认内存会话存储为Redis等分布式存储,确保多实例部署或应用重启时,刷新令牌不会丢失:
RedisOptions redisOptions = new RedisOptions().setHost("your-redis-host"); Redis redis = Redis.createClient(vertx, redisOptions); SessionStore sessionStore = RedisSessionStore.create(vertx, redis); Router router = Router.router(vertx); router.route().handler(SessionHandler.create(sessionStore));
内容的提问来源于stack exchange,提问作者Lukas Weber
相关产品推荐
相关产品推荐

