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

Spring Boot 403不跳转Keycloak登录页及配置解析器异常排查

Spring Boot集成动态Keycloak配置未认证跳转问题排查

我正在搭建由独立Keycloak实例提供认证能力的Spring Boot API,所有服务运行在本地docker swarm/compose环境中。问题表现为:当引导用户访问被@RolesAllowed("ROLE_USER")注解拦截的/api/v3/login接口时,返回标准白标错误页,提示There was an unexpected error (type=Forbidden, status=403),我预期浏览器会跳转至Keycloak客户端登录页。


相关配置

应用基础配置

该配置用于从数据库而非配置文件中拉取Keycloak客户端配置,我们会根据用户Cookie中携带的邮箱域名匹配不同客户端:

@ComponentScan({"com.mycompany"})
@Configuration
@EnableJpaRepositories(basePackages = "com.mycompany")
@EntityScan("com.mycompany")
public class ApplicationConfiguration {
    ...
    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new CustomKeycloakConfigResolver();
    }
}

自定义Keycloak配置解析器

public class CustomKeycloakConfigResolver implements KeycloakConfigResolver {
    @Autowired
    private KeycloakConfigService keycloakConfigService;
    ...
    @Override
    @Transactional
    public KeycloakDeployment resolve(final HttpFacade.Request request) {
        HttpFacade.Cookie cookie = request.getCookie("authDomain");
        if (cookie == null) {
            return generateNullDeployment();
        }
        
        final Pageable defaultPaging = PageRequest.of(0,1,Sort.by("id").ascending());
        Page<KeycloakConfig> page = keycloakConfigService.readConfigsByFilter(
            "domain", cookie.getValue(), defaultPaging
        );
        
        if ((page == null) || (page.getContent().size() < 1)) {
            return generateNullDeployment();
        }

        KeycloakConfig config = page.getContent().get(0);
        AdapterConfig adapterConfig = new AdapterConfig();
        adapterConfig.setRealm(config.getRealm());
        adapterConfig.setResource(config.getResource());
        adapterConfig.setPublicClient(config.getIsPublic());
        adapterConfig.setAuthServerUrl(config.getAuthServerUrl());
        adapterConfig.setSslRequired(
                config.getIsSslRequired() ? "all" : "none"
        );
        adapterConfig.setUseResourceRoleMappings(
                config.getUseResourceRoleMappings()
        );
        adapterConfig.setTokenStore(config.getTokenStore());
        adapterConfig.setBearerOnly(config.getBearerOnly());

        KeycloakDeployment keycloakDeployment =
                KeycloakDeploymentBuilder.build(adapterConfig);

        LOGGER.info("Keycloak Deployment Realm:    {}", keycloakDeployment.getRealm());
        LOGGER.info("Keycloak Deployment Resource: {}", keycloakDeployment.getResourceName());
        LOGGER.info("Keycloak Deployment URL:      {}", keycloakDeployment.getAuthUrl());

        return keycloakDeployment;
    }

注:以上配置运行正常,仅单次请求会触发该resolve方法调用数十次,运行日志如下:

...
o.k.adapters.KeycloakConfigResolver      : Keycloak Deployment Realm:    SpringBootKeycloak
o.k.adapters.KeycloakConfigResolver      : Keycloak Deployment Resource: SpringBootKeycloak
o.keycloak.adapters.KeycloakDeployment   : Loaded URLs from http://auth-service:8080/auth/realms/SpringBootKeycloak/.well-known/openid-configuration
...
o.k.adapters.KeycloakConfigResolver      : Keycloak Deployment Realm:    SpringBootKeycloak
o.k.adapters.KeycloakConfigResolver      : Keycloak Deployment Resource: SpringBootKeycloak
o.keycloak.adapters.KeycloakDeployment   : Loaded URLs from http://auth-service:8080/auth/realms/SpringBootKeycloak/.well-known/openid-configuration
...

最终可得到有效的KeycloakDeployment,认证登录URL为http://auth-service:8080/auth。

安全配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfiguration
        extends KeycloakWebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .csrf().disable()
                .antMatcher("/**")
                .authorizeRequests();
...

因此所有请求都被授权,API端点代码如下:

...
    @RolesAllowed("ROLE_USER")
    @GetMapping(
            value = "/login",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ResponseBody
    public Map<String, String> login() {
        final Map<String, String> response = new HashMap<String, String>();
        response.put("status", "OK");
        return response;
    }
...

我仅想通过该接口判断用户是否完成认证。预期未认证用户访问该接口时跳转至Keycloak登录页,但实际仅返回403禁止访问白页。
我猜测原因是安全配置中使用了.authorizeRequests(),未认证用户会被分配“Anonymous”角色,但我始终找不到正确的配置组合,实现未认证用户访问登录端点时跳转至KeycloakDeployment对应登录页的效果。


排查更新

我已经解开部分谜题:
我类路径下有一个遗留的AuthenticationEntryPoint类:

@ControllerAdvice
public class CustomAuthenticationEntryPoint
        implements AuthenticationEntryPoint {

即使我从未通过.authenticationEntryPoint()指定使用它,Spring Boot自动配置机制仍自动识别并加载了该类。

我完全禁用该类后,现在至少能从/api/v3/login跳转至/sso/login了,但是/sso/login路径不会触发CustomKeycloakConfigResolver,导致无法获取KeycloakDeployment,抛出如下异常:

rest-api_1            | 2021-12-02 21:59:20.871  WARN 12 --- [nio-8080-exec-5] o.keycloak.adapters.KeycloakDeployment   : Failed to load URLs from null/realms/null/.well-known/openid-configuration
rest-api_1            |
rest-api_1            | java.lang.IllegalStateException: Target host is null
rest-api_1            |     at org.apache.http.util.Asserts.notNull(Asserts.java:52) ~[httpcore-4.4.14.jar!/:4.4.14]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:24:07