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

Keycloak securityContext为空,如何获取已登录用户详情?

问题描述

已从以下地址获取access token:

https://localhost:1099/auth/realms/myrealm/protocol/openid-connect/token

调用/myresources端点时,尝试从HttpSession或HttpServletRequest属性中获取KeycloakSecurityContext,但始终返回No Keycloak Security Context found,securityContext和principal均为空。使用嵌入式Spring Boot Keycloak 21.0.0版本,未集成Spring Security,需要从access token中提取preferred_username等用户详情。

端点代码如下:

import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
public class userDetails {

    @GetMapping(value = "/myresources", produces = MediaType.APPLICATION_JSON_VALUE)
    public String handleUserInfoRequest(HttpServletRequest request) {
        System.out.println("Using HttpServletRequest to inspect authentication...");

        // 尝试从Session获取
        HttpSession session = request.getSession();
        KeycloakSecurityContext securityContext = (KeycloakSecurityContext) session.getAttribute(KeycloakSecurityContext.class.getName());

        // 也尝试过从Request属性获取
        // KeycloakSecurityContext securityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());

        if (securityContext != null) {
            AccessToken token = securityContext.getToken();
            String userId = token.getId();
            String userName = token.getPreferredUsername();

            String securityContextDetails = "User ID: " + userId + "\n" +
                                           "Username: " + userName;
            System.out.println("Security Context Details:\n" + securityContextDetails);
            return securityContextDetails;
        } else {
            return "No Keycloak Security Context found";
        }
    }
}
解决方案

方法一:配置Keycloak Servlet过滤器(推荐)

未集成Spring Security时,需手动配置Keycloak Servlet过滤器,让它自动解析请求中的access token并将KeycloakSecurityContext注入到request或session中。

  1. 添加Keycloak Servlet依赖
    在pom.xml中加入:
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-servlet-filter</artifactId>
    <version>21.0.0</version>
</dependency>
  1. 注册Keycloak过滤器
    创建配置类注册过滤器:
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KeycloakFilterConfig {

    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    public FilterRegistrationBean<KeycloakOIDCFilter> keycloakOIDCFilterRegistrationBean() {
        FilterRegistrationBean<KeycloakOIDCFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new KeycloakOIDCFilter());
        registrationBean.addUrlPatterns("/myresources/*"); // 指定需要拦截的路径
        registrationBean.setOrder(1); // 设置过滤器优先级
        return registrationBean;
    }
}
  1. 配置Keycloak客户端信息
    在application.properties中添加:
keycloak.realm=myrealm
keycloak.auth-server-url=https://localhost:1099/auth
keycloak.resource=你的客户端ID
keycloak.credentials.secret=你的客户端密钥
keycloak.public-client=false
keycloak.bearer-only=true # 资源服务仅验证token,不重定向登录

配置完成后,请求/myresources时需在请求头携带Authorization: Bearer <你的access-token>,过滤器会自动解析token并注入KeycloakSecurityContext,原有代码即可正常提取用户信息。

方法二:手动解析Access Token

若不想配置过滤器,可直接从请求头获取token并手动解析:

import org.keycloak.TokenVerifier;
import org.keycloak.representations.AccessToken;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
public class userDetails {

    @GetMapping(value = "/myresources", produces = MediaType.APPLICATION_JSON_VALUE)
    public String handleUserInfoRequest(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return "未提供access token";
        }

        String accessToken = authHeader.substring(7); // 移除"Bearer "前缀

        try {
            // 使用Keycloak TokenVerifier验证并解析(生产环境推荐,会校验签名和有效期)
            TokenVerifier<AccessToken> verifier = TokenVerifier.create(accessToken, AccessToken.class);
            // 如需严格验证,可配置Keycloak公钥或让验证器连接服务器校验
            // verifier.withPublicKey(你的Keycloak公钥);
            AccessToken token = verifier.verify().getToken();

            String userId = token.getId();
            String userName = token.getPreferredUsername();
            String email = token.getEmail();

            return String.format("User ID: %s\nUsername: %s\nEmail: %s", userId, userName, email);

            // 仅测试用:跳过签名校验直接解析JWT payload
            // String[] parts = accessToken.split("\\.");
            // return new String(Base64.getUrlDecoder().decode(parts[1]));
        } catch (Exception e) {
            return "无效的access token: " + e.getMessage();
        }
    }
}

注意:跳过签名校验存在安全风险,生产环境必须使用TokenVerifier确保token合法性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 08:52:33