Keycloak问题:令牌过期后Ajax调用返回401未授权
Keycloak Ajax请求令牌无法自动刷新:原因与解决方案
首先可以明确地说:这是Keycloak 3.4.3 Java Adapter的正常行为,不是你的配置问题。下面详细解释原因并给出可行的解决方案:
为什么Ajax请求不会自动刷新令牌?
Keycloak的Java(Spring Boot)适配器默认仅对浏览器发起的普通页面请求处理令牌刷新逻辑——它会通过请求的Cookie、User-Agent等标识判断请求是否来自浏览器,并且要求请求支持重定向(因为令牌刷新过程可能需要跳转到Keycloak服务器)。
而带有X-Requested-With: XMLHttpRequest的异步请求,会被适配器判定为非浏览器端的API请求,这类请求通常不支持重定向跳转,所以适配器不会自动触发令牌刷新流程。
另外,你的配置本身是完全符合公开客户端要求的,没有问题:
{ "realm": "real name", "auth-server-url": "http://172.21.34.65/auth", "ssl-required": "external", "resource": "appName", "public-client": true, "use-resource-role-mappings": true }
解决方案
1. 官方推荐:用Keycloak JS Adapter前置处理
这是最标准的方案:在前端发起Ajax请求前,利用JS Adapter主动检查令牌有效期,若即将过期则先刷新令牌,再携带新令牌发起请求。
示例前端代码:
// 初始化JS Adapter(配置和Java Adapter对齐) const keycloak = Keycloak({ realm: "real name", authServerUrl: "http://172.21.34.65/auth", clientId: "appName" }); // 封装Ajax请求前的令牌检查逻辑 function sendAjaxRequest(url, options = {}) { // 若令牌剩余有效期少于30秒则自动刷新 return keycloak.updateToken(30) .then(() => { // 携带新的Bearer令牌发起请求 const headers = { ...options.headers, 'Authorization': `Bearer ${keycloak.token}`, 'X-Requested-With': 'XMLHttpRequest' }; return fetch(url, {...options, headers}); }) .catch(() => { // 刷新失败,引导用户重新登录 keycloak.login(); throw new Error('令牌过期,需重新登录'); }); } // 使用示例 sendAjaxRequest('/your-api-endpoint') .then(res => res.json()) .then(data => console.log('请求结果:', data));
2. 备选方案:自定义Spring过滤器强制处理Ajax请求刷新
如果你不想引入JS Adapter,可以自定义一个Spring过滤器,在Keycloak过滤器之前拦截Ajax请求,手动检查并刷新令牌:
示例后端代码:
@Component public class AjaxTokenRefreshFilter extends OncePerRequestFilter { @Autowired private KeycloakSecurityContextRepository securityContextRepository; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 判断是否是Ajax请求 String requestedWith = request.getHeader("X-Requested-With"); if ("XMLHttpRequest".equals(requestedWith)) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth instanceof KeycloakAuthenticationToken) { KeycloakSecurityContext securityContext = ((KeycloakAuthenticationToken) auth).getAccount().getKeycloakSecurityContext(); // 检查令牌是否即将过期(剩余时间少于60秒) long remainingTime = securityContext.getToken().getExp() - System.currentTimeMillis() / 1000; if (remainingTime < 60) { try { // 调用Keycloak接口刷新令牌 securityContext.refreshToken(); // 更新上下文保存新令牌 securityContextRepository.saveContext(securityContext, request, response); } catch (KeycloakSecurityException e) { // 刷新失败,返回401让前端处理登录 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } } } } filterChain.doFilter(request, response); } }
注意:需要在Spring Security配置中,将这个自定义过滤器的顺序设置在Keycloak过滤器之前。
总结
你的配置完全没问题,Ajax请求无法自动刷新是Keycloak Java Adapter的设计逻辑导致的。优先推荐使用JS Adapter前置刷新令牌,这是官方针对前端异步请求的标准处理方式,也更符合前后端分离的架构思路。
内容的提问来源于stack exchange,提问作者Sorbete
相关产品推荐
相关产品推荐

