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

集成Keycloak的Spring Boot响应式应用无法查看Access/Refresh Token

查看Access Token和Refresh Token的方法

为什么浏览器看不到令牌?

Spring Security 5.7.x的OAuth2 Client在WebFlux模式下,默认将Access Token和Refresh Token存储在服务器端的会话中,浏览器仅保留SESSION ID用于关联会话,所以直接在Cookie里看不到令牌本身。


1. 后端调试接口获取(推荐调试用)

写一个临时接口,从Spring Security的上下文里提取令牌,仅用于本地调试,生产环境务必删除:

import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class TokenDebugController {

    @GetMapping("/debug/tokens")
    public Mono<String> getTokens(@RegisteredOAuth2AuthorizedClient("keycloak") OAuth2AuthorizedClient client) {
        return Mono.just(String.format(
            "Access Token: %s\nRefresh Token: %s",
            client.getAccessToken().getTokenValue(),
            client.getRefreshToken() != null ? client.getRefreshToken().getTokenValue() : "无"
        ));
    }
}

访问/debug/tokens就能看到当前登录用户的令牌内容。

2. 抓包查看授权流程中的令牌

用浏览器开发者工具(F12)抓包:

  • 启动登录流程,完成Keycloak账号验证后
  • 在Network标签下过滤XHR请求,找到你的应用向Keycloak /token 端点发送的POST请求
  • 查看该请求的响应体,里面包含完整的access_token和refresh_token字段

3. Keycloak管理后台查看

登录Keycloak控制台:

  1. 进入目标Realm
  2. 点击左侧菜单Users,找到对应的用户
  3. 切换到Sessions标签页,能看到该用户的活跃会话,点击查看可获取Access Token的解析内容(Refresh Token不会显示完整值)

4. 修改令牌存储到Cookie(不推荐生产)

如果非要在浏览器Cookie里看到令牌(风险极高,仅调试用),可以修改Security配置,将令牌写入Cookie:

import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.http.ResponseCookie;

@EnableWebFluxSecurity
public class SecurityConfig {

    private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;

    public SecurityConfig(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.authorizedClientRepository = authorizedClientRepository;
    }

    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
            .oauth2Login(oauth2 -> oauth2
                .authenticationSuccessHandler(cookieTokenSuccessHandler())
            );
        return http.build();
    }

    private ServerAuthenticationSuccessHandler cookieTokenSuccessHandler() {
        return (exchange, authentication) -> {
            OAuth2AuthenticationToken authToken = (OAuth2AuthenticationToken) authentication;
            return authorizedClientRepository.loadAuthorizedClient(
                    authToken.getAuthorizedClientRegistrationId(),
                    authentication,
                    exchange
                )
                .flatMap(client -> writeTokensToCookie(exchange, client))
                .then(Mono.empty());
        };
    }

    private Mono<Void> writeTokensToCookie(ServerWebExchange exchange, OAuth2AuthorizedClient client) {
        var response = exchange.getResponse();
        // 写入Access Token Cookie(httpOnly设为false才能在浏览器看到,生产绝对不能这么做)
        response.addCookie(ResponseCookie.from("ACCESS_TOKEN", client.getAccessToken().getTokenValue())
            .path("/")
            .httpOnly(false)
            .build());
        // 写入Refresh Token Cookie
        if (client.getRefreshToken() != null) {
            response.addCookie(ResponseCookie.from("REFRESH_TOKEN", client.getRefreshToken().getTokenValue())
                .path("/")
                .httpOnly(false)
                .build());
        }
        return Mono.empty();
    }
}

⚠️ 警告:生产环境禁止将令牌存在浏览器Cookie,更不能设置httpOnly=false,会导致XSS攻击窃取令牌,引发安全事故。


内容的提问来源于stack exchange,提问作者noble

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 08:05:00