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

Spring Boot集成Keycloak认证授权——策略执行器配置优化

解决Keycloak Policy Enforcer配置硬编码问题

问题背景

在Spring Boot应用中完全基于Keycloak实现认证授权,当前通过policy-enforcer.json配置Keycloak策略执行器,但JSON文件中的realm、认证服务器地址、客户端信息均为硬编码,无法使用占位符动态替换。需要将这些配置项迁移到application.properties或配置类中读取,同时保留Keycloak处理所有认证授权逻辑的核心机制。

当前代码实现:

SecurityConfig类

import org.keycloak.adapters.authorization.integration.jakarta.ServletPolicyEnforcerFilter;
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
import org.keycloak.adapters.authorization.spi.HttpRequest;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.util.JsonSerialization;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;

import java.io.IOException;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf(AbstractHttpConfigurer::disable);
        httpSecurity.addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter.class);

        httpSecurity.sessionManagement(t -> t.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return httpSecurity.build();
    }

    private ServletPolicyEnforcerFilter createPolicyEnforcerFilter() {
        return new ServletPolicyEnforcerFilter(new ConfigurationResolver() {
            @Override
            public PolicyEnforcerConfig resolve(HttpRequest httpRequest) {
                try {
                    return JsonSerialization.readValue(getClass().getResourceAsStream("/policy-enforcer.json"),
                            PolicyEnforcerConfig.class);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
}

policy-enforcer.json文件

{
  "realm": "realm-name",
  "auth-server-url": "server-url",
  "resource": "client-name",
  "credentials": {
    "secret": "client-secret"
  },
  "http-method-as-scope": true,
  "paths": [
    {
      "path": "/path-1",
      "enforcement-mode": "DISABLED"
    },
    {
      "path": "/path-2",
      "enforcement-mode": "DISABLED"
    }
  ]
}

优化方案

1. 在application.properties中添加Keycloak配置项

将硬编码的配置参数移到配置文件中:

# Keycloak核心配置
keycloak.realm=realm-name
keycloak.auth-server-url=server-url
keycloak.resource=client-name
keycloak.credentials.secret=client-secret
keycloak.http-method-as-scope=true

# 可选:路径规则配置(也可保留在JSON中)
keycloak.paths[0].path=/path-1
keycloak.paths[0].enforcement-mode=DISABLED
keycloak.paths[1].path=/path-2
keycloak.paths[1].enforcement-mode=DISABLED

2. 创建配置类绑定属性

通过@ConfigurationProperties注解将配置文件中的属性绑定到Java类:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
@ConfigurationProperties(prefix = "keycloak")
public class KeycloakProperties {
    private String realm;
    private String authServerUrl;
    private String resource;
    private Credentials credentials;
    private boolean httpMethodAsScope;
    private List<PathConfig> paths;

    // Getters & Setters
    public static class Credentials {
        private String secret;
        public String getSecret() { return secret; }
        public void setSecret(String secret) { this.secret = secret; }
    }

    public static class PathConfig {
        private String path;
        private String enforcementMode;
        public String getPath() { return path; }
        public void setPath(String path) { this.path = path; }
        public String getEnforcementMode() { return enforcementMode; }
        public void setEnforcementMode(String enforcementMode) { this.enforcementMode = enforcementMode; }
    }

    public String getRealm() { return realm; }
    public void setRealm(String realm) { this.realm = realm; }
    public String getAuthServerUrl() { return authServerUrl; }
    public void setAuthServerUrl(String authServerUrl) { this.authServerUrl = authServerUrl; }
    public String getResource() { return resource; }
    public void setResource(String resource) { this.resource = resource; }
    public Credentials getCredentials() { return credentials; }
    public void setCredentials(Credentials credentials) { this.credentials = credentials; }
    public boolean isHttpMethodAsScope() { return httpMethodAsScope; }
    public void setHttpMethodAsScope(boolean httpMethodAsScope) { this.httpMethodAsScope = httpMethodAsScope; }
    public List<PathConfig> getPaths() { return paths; }
    public void setPaths(List<PathConfig> paths) { this.paths = paths; }
}

3. 修改SecurityConfig动态构建配置

注入配置类,动态替换PolicyEnforcerConfig中的硬编码信息:

import org.keycloak.adapters.authorization.integration.jakarta.ServletPolicyEnforcerFilter;
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
import org.keycloak.adapters.authorization.spi.HttpRequest;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.util.JsonSerialization;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final KeycloakProperties keycloakProperties;

    // 构造注入配置属性
    public SecurityConfig(KeycloakProperties keycloakProperties) {
        this.keycloakProperties = keycloakProperties;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf(AbstractHttpConfigurer::disable);
        httpSecurity.addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter.class);
        httpSecurity.sessionManagement(t -> t.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        return httpSecurity.build();
    }

    private ServletPolicyEnforcerFilter createPolicyEnforcerFilter() {
        return new ServletPolicyEnforcerFilter(new ConfigurationResolver() {
            @Override
            public PolicyEnforcerConfig resolve(HttpRequest httpRequest) {
                try {
                    // 初始化配置(可选择保留JSON中的路径配置,或完全从配置类读取)
                    PolicyEnforcerConfig config;
                    if (keycloakProperties.getPaths() == null || keycloakProperties.getPaths().isEmpty()) {
                        // 从JSON读取路径配置
                        config = JsonSerialization.readValue(
                                getClass().getResourceAsStream("/policy-enforcer.json"),
                                PolicyEnforcerConfig.class
                        );
                    } else {
                        // 完全从配置类构建
                        config = new PolicyEnforcerConfig();
                        // 转换路径配置
                        List<PolicyEnforcerConfig.PathConfig> policyPaths = keycloakProperties.getPaths().stream()
                                .map(propPath -> {
                                    PolicyEnforcerConfig.PathConfig pathConfig = new PolicyEnforcerConfig.PathConfig();
                                    pathConfig.setPath(propPath.getPath());
                                    pathConfig.setEnforcementMode(propPath.getEnforcementMode());
                                    return pathConfig;
                                })
                                .collect(Collectors.toList());
                        config.setPaths(policyPaths);
                    }

                    // 覆盖核心配置项
                    config.setRealm(keycloakProperties.getRealm());
                    config.setAuthServerUrl(keycloakProperties.getAuthServerUrl());
                    config.setResource(keycloakProperties.getResource());

                    PolicyEnforcerConfig.Credentials credentials = new PolicyEnforcerConfig.Credentials();
                    credentials.setSecret(keycloakProperties.getCredentials().getSecret());
                    config.setCredentials(credentials);

                    config.setHttpMethodAsScope(keycloakProperties.isHttpMethodAsScope());

                    return config;
                } catch (IOException e) {
                    throw new RuntimeException("加载策略执行器配置失败", e);
                }
            }
        });
    }
}

4. 可选:移除policy-enforcer.json文件

如果所有配置都已迁移到application.properties,可以直接删除policy-enforcer.json文件,完全通过配置类动态构建PolicyEnforcerConfig。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:55:33