You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:59:14