集成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控制台:
- 进入目标Realm
- 点击左侧菜单Users,找到对应的用户
- 切换到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
相关产品推荐
相关产品推荐

