将Keycloak弹簧适配器与Spring Boot 3一起使用

Use Keycloak Spring Adapter with Spring Boot 3

提问人:Samuel 提问时间:11/25/2022 最后编辑:Dmitriy PopovSamuel 更新时间:8/22/2023 访问量:28941

问:

我在使用 Keycloak Spring Adapter 的项目中更新到 Spring Boot 3。不幸的是,它没有启动,因为它首先在 Spring Security 中被弃用,然后被删除。目前有没有另一种方法可以用Keycloak实现安全性?或者换句话说:如何将Spring Boot 3与Keycloak适配器结合使用?KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter

我在互联网上搜索了一下,但找不到适配器的任何其他版本。

弹簧靴 钥匙斗篷

评论

1赞 Danylo Zatorsky 5/1/2023
有关Keycloak适配器弃用的最新更新,请访问:keycloak.org/2023/03/adapter-deprecation-update.html

答:

5赞 lathspell 11/25/2022 #1

使用标准的 Spring Security OAuth2 客户端而不是特定的 Keycloak 适配器,而不是 .SecurityFilterChainWebSecurityAdapter

像这样的东西:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
class OAuth2SecurityConfig {

@Bean
fun customOauth2FilterChain(http: HttpSecurity): SecurityFilterChain {
    log.info("Configure HttpSecurity with OAuth2")

    http {
        oauth2ResourceServer {
            jwt { jwtAuthenticationConverter = CustomBearerJwtAuthenticationConverter() }
        }
        oauth2Login {}

        csrf { disable() }

        authorizeRequests {
            // Kubernetes
            authorize("/readiness", permitAll)
            authorize("/liveness", permitAll)
            authorize("/actuator/health/**", permitAll)
            // ...
            // everything else needs at least a valid login, roles are checked at method level
            authorize(anyRequest, authenticated)
        }
    }

    return http.build()
}

然后在:application.yml

spring:
  security:
    oauth2:
      client:
        provider:
          abc:
            issuer-uri: https://keycloak.../auth/realms/foo
        registration:
          abc:
            client-secret: ...
            provider: abc
            client-id: foo
            scope: [ openid, profile, email ]
      resourceserver:
        jwt:
          issuer-uri: https://keycloak.../auth/realms/foo

评论

0赞 Samuel 11/25/2022
感谢您发布您的配置。我会尝试这种方式。Keycloak已经宣布放弃他们自己的适配器。
0赞 ch4mp 11/25/2022
缺少角色映射,一旦需要任何基于角色的访问控制(表达式或在安全配置中),这将成为一个问题。此外,这仅涵盖部分情况:不包括资源服务器 (REST API)。@PreAuthoriseauthoriseRequests
0赞 Samuel 11/25/2022
您在代码中提到了一个读取 CustomBearerJwtAuthenticationConverter() 的方法。我从哪里获得该方法?
0赞 ch4mp 11/26/2022
只需实现 Spring 的接口即可。您可能会在我对资源服务器的回答中找到身份验证转换器的灵感。
61赞 ch4mp 11/25/2022 #2

由于您发现的原因,您不能将 Keycloak 适配器与 spring-boot 3 一起使用,以及其他一些与传递依赖项相关的适配器。由于大多数 Keycloak 适配器在 2022 年初被弃用,因此很可能不会发布更新来解决这个问题。

相反,请将 spring-security 6 库用于 OAuth2不要惊慌,使用 spring-boot 这是一项简单的任务

在下文中,我将认为您对 OAuth2 概念有很好的理解,并且确切地知道为什么需要配置 OAuth2 客户端或 OAuth2 资源服务器。如有疑问,请参阅我的教程OAuth2 要点部分

我在这里只详细介绍servlet应用程序作为资源服务器的配置,然后作为客户端,对于单个Keycloak领域,有和没有,我的Spring Boot启动器。直接浏览到你感兴趣的部分(但如果你不想使用“我的”启动器,请准备好编写更多的代码)。spring-addons-starter-oidc

另请参阅我的教程,了解不同的用例,例如:

  • 接受由多个领域或实例(预先已知或在受信任域中动态创建)颁发的令牌
  • 反应式应用程序(Webflux),例如spring-cloud-gateway
  • 公开提供 REST API 和服务器端呈现的 UI 以使用它的应用
  • 高级访问控制规则
  • BFF模式
  • ...

1. OAuth2 资源服务器

应用公开了受访问令牌保护的 REST API。它由 OAuth2 REST 客户端使用。此类客户端的几个示例:

  • 另一个配置为 OAuth2 客户端并使用 、 或类似方式的 Spring 应用程序WebClient@FeignClientRestTemplate
  • 像 Postman 这样的开发工具,能够获取 OAuth2 令牌并发出 REST 请求
  • 基于 Javascript 的应用程序配置为“公共”OAuth2 客户端,带有类似 angular-auth-oidc-client 的库

1.1. 使用spring-addons-starter-oidc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-starter-oidc</artifactId>
    <version>7.1.3</version>
</dependency>
origins: http://localhost:4200
issuer: http://localhost:8442/realms/master

com:
  c4-soft:
    springaddons:
      oidc:
        ops:
        - iss: ${issuer}
          username-claim: preferred_username
          authorities:
          - path: $.realm_access.roles
            prefix: ROLE_
          - path: $.resource_access.*.roles
        resourceserver:
          cors:
          - path: /**
            allowed-origin-patterns: ${origins}
          permit-all: 
          - "/actuator/health/readiness"
          - "/actuator/health/liveness"
          - "/v3/api-docs/**"

上面的 conf 中领域角色的前缀仅用于说明目的,您可以将其删除。CORS 配置也需要一些改进。

@Configuration
@EnableMethodSecurity
public static class WebSecurityConfig { }

无需再为资源服务器配置经过微调的 CORS 策略和颁发机构映射。战利品,不是吗?

从属性是一个数组中可以猜到,这个解决方案实际上与“静态”多租户兼容:你可以根据需要声明任意数量的受信任颁发者,它可以是异构的(对用户名和权限使用不同的声明)。ops

此外,该解决方案与响应式应用程序兼容:将从类路径上的内容中检测它并调整其安全自动配置。spring-addons-starter-oidc

1.2. 只需spring-boot-starter-oauth2-resource-server

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <!-- used when converting Keycloak roles to Spring authorities -->
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
</dependency>
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8442/realms/master
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public static class WebSecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http, Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter) throws Exception {

        http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)));

        // Enable and configure CORS
        http.cors(cors -> cors.configurationSource(corsConfigurationSource("http://localhost:4200")));

        // State-less session (state in access-token only)
        http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        // Disable CSRF because of state-less session-management
        http.csrf(csrf -> csrf.disable());

        // Return 401 (unauthorized) instead of 302 (redirect to login) when
        // authorization is missing or invalid
        http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) -> {
            response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"");
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }));

        // @formatter:off
        http.authorizeHttpRequests(accessManagement -> accessManagement
            .requestMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs/**").permitAll()
            .anyRequest().authenticated()
        );
        // @formatter:on

        return http.build();
    }

    private UrlBasedCorsConfigurationSource corsConfigurationSource(String... origins) {
        final var configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(origins));
        configuration.setAllowedMethods(List.of("*"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setExposedHeaders(List.of("*"));

        final var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @RequiredArgsConstructor
    static class JwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<? extends GrantedAuthority>> {

        @Override
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public Collection<? extends GrantedAuthority> convert(Jwt jwt) {
            return Stream.of("$.realm_access.roles", "$.resource_access.*.roles").flatMap(claimPaths -> {
                Object claim;
                try {
                    claim = JsonPath.read(jwt.getClaims(), claimPaths);
                } catch (PathNotFoundException e) {
                    claim = null;
                }
                if (claim == null) {
                    return Stream.empty();
                }
                if (claim instanceof String claimStr) {
                    return Stream.of(claimStr.split(","));
                }
                if (claim instanceof String[] claimArr) {
                    return Stream.of(claimArr);
                }
                if (Collection.class.isAssignableFrom(claim.getClass())) {
                    final var iter = ((Collection) claim).iterator();
                    if (!iter.hasNext()) {
                        return Stream.empty();
                    }
                    final var firstItem = iter.next();
                    if (firstItem instanceof String) {
                        return (Stream<String>) ((Collection) claim).stream();
                    }
                    if (Collection.class.isAssignableFrom(firstItem.getClass())) {
                        return (Stream<String>) ((Collection) claim).stream().flatMap(colItem -> ((Collection) colItem).stream()).map(String.class::cast);
                    }
                }
                return Stream.empty();
            })
            /* Insert some transformation here if you want to add a prefix like "ROLE_" or force upper-case authorities */
            .map(SimpleGrantedAuthority::new)
            .map(GrantedAuthority.class::cast).toList();
        }
    }

    @Component
    @RequiredArgsConstructor
    static class SpringAddonsJwtAuthenticationConverter implements Converter<Jwt, JwtAuthenticationToken> {

        @Override
        public JwtAuthenticationToken convert(Jwt jwt) {
            final var authorities = new JwtGrantedAuthoritiesConverter().convert(jwt);
            final String username = JsonPath.read(jwt.getClaims(), "preferred_username");
            return new JwtAuthenticationToken(jwt, authorities, username);
        }
    }
}

除了比前一个解决方案更冗长之外,此解决方案还不太灵活:

  • 不适用于多租户(多个Keycloak领域或实例)
  • 硬编码允许的源
  • 硬编码的声明名称以从中获取自动性
  • 硬编码的“permitAll”路径匹配器

2. OAuth2 客户端

应用公开使用会话(而不是访问令牌)保护的任何类型的资源。它由浏览器(或任何其他能够维护会话的用户代理)直接使用,不需要脚本语言或 OAuth2 客户端库(授权代码流、注销和令牌存储由服务器上的 Spring 处理)。常见用例包括:

  • 具有服务器端呈现 UI 的应用程序(使用 Thymeleaf、JSF 或其他任何方法)
  • spring-cloud-gateway用作 Backend F 或 Frontend:配置为带有过滤器的 OAuth2 客户端(在将请求转发到下游资源服务器之前,从浏览器中隐藏 OAuth2 令牌,并用访问令牌替换会话 cookie)TokenRelay

请注意,Back-Channel Logout 尚未由 Spring 实现。如果需要,请使用“我的”启动器(或从中复制)。

2.1. 使用spring-addons-starter-oidc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-client</artifactId>
</dependency>
<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-starter-oidc</artifactId>
    <version>7.1.3</version>
</dependency>
issuer: http://localhost:8442/realms/master
client-id: spring-addons-confidential
client-secret: change-me
client-uri: http://localhost:8080

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: ${issuer}
        registration:
          keycloak-login:
            authorization-grant-type: authorization_code
            client-name: My Keycloak instance
            client-id: ${client-id}
            client-secret: ${client-secret}
            provider: keycloak
            scope: openid,profile,email,offline_access

com:
  c4-soft:
    springaddons:
      oidc:
        ops:
        - iss: ${issuer}
          username-claim: preferred_username
          authorities:
          - path: $.realm_access.roles
          - path: $.resource_access.*.roles
        client:
          client-uri: ${client-uri}
          security-matchers: /**
          permit-all:
          - /
          - /login/**
          - /oauth2/**
          csrf: cookie-accessible-from-js
          post-login-redirect-path: /home
          post-logout-redirect-path: /
          back-channel-logout-enabled: true
@Configuration
@EnableMethodSecurity
public class WebSecurityConfig {
}

至于资源服务器,此解决方案也适用于反应式应用程序。

客户端上还有一个可选的多租户支持:允许用户同时登录多个OpenID提供程序,他可能有不同的用户名(默认情况下,这是Keycloak中的UUID,并随每个领域而变化)。subject

2.2. 只需spring-boot-starter-oauth2-client

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <!-- used when converting Keycloak roles to Spring authorities -->
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
</dependency>
issuer: http://localhost:8442/realms/master
client-id: spring-addons-confidential
client-secret: change-me

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: ${issuer}
        registration:
          keycloak-login:
            authorization-grant-type: authorization_code
            client-name: My Keycloak instance
            client-id: ${client-id}
            client-secret: ${client-secret}
            provider: keycloak
            scope: openid,profile,email,offline_access
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {

    @Bean
    SecurityFilterChain
            clientSecurityFilterChain(HttpSecurity http, InMemoryClientRegistrationRepository clientRegistrationRepository)
                    throws Exception {
        http.oauth2Login(withDefaults());
        http.logout(logout -> {
            logout.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository));
        });
        // @formatter:off
        http.authorizeHttpRequests(ex -> ex
                .requestMatchers("/", "/login/**", "/oauth2/**").permitAll()
                .requestMatchers("/nice.html").hasAuthority("NICE")
                .anyRequest().authenticated());
        // @formatter:on
        return http.build();
    }

    @Component
    @RequiredArgsConstructor
    static class GrantedAuthoritiesMapperImpl implements GrantedAuthoritiesMapper {

        @Override
        public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                if (OidcUserAuthority.class.isInstance(authority)) {
                    final var oidcUserAuthority = (OidcUserAuthority) authority;
                    final var issuer = oidcUserAuthority.getIdToken().getClaimAsURL(JwtClaimNames.ISS);
                    mappedAuthorities.addAll(extractAuthorities(oidcUserAuthority.getIdToken().getClaims()));

                } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                    try {
                        final var oauth2UserAuthority = (OAuth2UserAuthority) authority;
                        final var userAttributes = oauth2UserAuthority.getAttributes();
                        final var issuer = new URL(userAttributes.get(JwtClaimNames.ISS).toString());
                        mappedAuthorities.addAll(extractAuthorities(userAttributes));

                    } catch (MalformedURLException e) {
                        throw new RuntimeException(e);
                    }
                }
            });

            return mappedAuthorities;
        };

        @SuppressWarnings({ "rawtypes", "unchecked" })
        private static Collection<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {
            /* See resource server solution above for authorities mapping */
        }
    }
}

3. 什么是spring-addons-starter-oidc,为什么要使用它

此启动器是标准的 Spring Boot 启动器,具有其他应用程序属性,用于自动配置默认 Bean 并将其提供给 Spring Security。需要注意的是,自动配置的@Beans几乎都是@ConditionalOnMissingBean这使您能够在 conf 中覆盖它

它是开源的,您可以更改它为您预先配置的所有内容(请参阅 Javadoc、入门 README 或许多示例)。在决定不信任它之前,您应该阅读启动器源代码,它并没有那么大。从 imports 资源开始,它定义了 Spring Boot 为自动配置加载的内容。

在我看来(如上所述),OAuth2 的 Spring Boot 自动配置可以进一步推动:

  • 使 OAuth2 配置更具可移植性:使用可配置的权限转换器,只需编辑属性(Keycloak、Auth0、Cognito、Azure AD 等),即可从 OIDC 提供程序切换到另一个提供程序。
  • 简化不同环境中的应用部署:CORS 配置由属性文件控制
  • 大幅减少 Java 代码量(如果您在多租户场景中,事情会变得更加复杂)
  • 默认支持多个颁发者
  • 减少错误配置的可能性(例如,经常看到在客户端上禁用 CSRF 保护的示例配置,或者在使用访问令牌保护的端点上浪费资源)

评论

2赞 Samuel 11/25/2022
谢谢。这看起来很有赃物,很容易迁移到。将尝试这种方法。
0赞 Samuel 11/26/2022
我目前正在实施您的建议。具有资源依赖关系的 rest 服务启动,但在访问任何 url 时,我收到错误,指出没有可用的 JwtDecoder。gitlab.com/samuel1998.schwanzer/SmartOrganizr/-/tree/migrate/......
0赞 Samuel 11/26/2022
我的本地配置如下所示:spring.security.oauth2.client.registration.keycloak.client-id=smartorganizr spring.security.oauth2.client.registration.keycloak.client-secret=dzmKY0QUuLflQBeceMIhPCr8gE5AN9YF spring.security.oauth2.client.provider.keycloak.issuer-uri=http://192.168.2.33/realms/master
0赞 Samuel 11/26/2022
不好意思。我想创建一个客户端。它应该具有与keycloak机密客户端相同的配置。您可以在 gitlab 上找到更新版本。这是我的yaml:pastebin.com/GFMqxPPE 密码:sGAydP4AyT
2赞 Samuel 5/24/2023
棒。感谢您对 Spring Boot 3.1/Security 6.1 的跟进。
1赞 Yasas Sandeepa 2/17/2023 #3

无法使用 Keycloak 适配器,因为继承自该类,该类在 Spring Security 中已弃用,随后在较新版本中删除。KeycloakWebSecurityConfigurerAdapterWebSecurityConfigurerAdapter

我在 Medium 上发表了一篇关于将 Keycloak 与 Spring Boot 3.0 集成的详细文章,其中提供了有关如何将 Keycloak 与 Spring Boot 3.0 集成的分步指南。

本指南对于那些不熟悉将Keycloak与Spring Boot 3.0集成或从旧版本迁移到Spring Boot 3.0的用户特别有用。

您可以查看文章 (https://medium.com/geekculture/using-keycloak-with-spring-boot-3-0-376fa9f60e0b) 以全面解释集成过程。enter image description here

希望这有帮助!如果您有任何问题、进一步的澄清或建议,请随时发表评论。

评论

1赞 ch4mp 2/17/2023
您是否意识到您的文章仅涵盖资源服务器和 *realm 角色,对吗?(当上面的答案也涵盖客户客户角色时......
0赞 Aguid 2/20/2023
我读了你的文章,它适用于spring-boot-starter-oath2-resource-server:)但仍然被spring-boot-starter-oath2-client阻止,它总是返回一个404!您是否有将spring-boot-starter-oath2-client与Thymleaf一起使用的教程?
0赞 Yasas Sandeepa 2/20/2023
嗨,@Aguid。正如我在文章中所描述的,如果你将它与 Thymleaf 一起使用,你必须使用 oath2-client。目前,我没有教程来指导您,但我会写一个。
0赞 Ghassen Jemaî 2/28/2023
你好亚萨斯,谢谢你的有用教程..我已经实现了您在教程中提到的代码,但是当我想访问用户URL时,我仍然会获得未经授权的访问,并且我已经仔细检查了是否已将用户角色分配给用户。
0赞 Ghassen Jemaî 2/28/2023
更新,我的问题已通过使用 keycloak 领域设置配置 application.properties 得到解决
-1赞 Ghassen Jemaî 2/28/2023 #4

Keycloak 21.0.0引入了一些新的变化来支持Spring Security 6.x.x和Spring Boot 3.x.x.。这是对它的引用

评论

2赞 Ghassen Jemaî 2/28/2023
该文档已弃用,因为它是从 WebSecurityConfigurerAdapater 扩展而来的,您仍然可以将 oauth2 客户端依赖项与 Spring Security 一起使用
0赞 Samuel 3/1/2023
不是还是一样吗?
0赞 Ghassen Jemaî 3/1/2023
是的,这些文档几乎已被弃用,因为它们实现了 WebSecurityConfigurationAdapater
0赞 ravthiru 4/29/2023 #5

Keycloak适配器已弃用,Keycloak团队宣布的未来不会有任何更新或修复。

建议使用 Spring Security 提供的 OAuth2 和 OpenID Connect 支持。

-1赞 Andrey 7/16/2023 #6

根据不同的资源和整个周末,我花了来解决这个新问题,我设法找到了完美的解决方案。

我定义了 2 个角色:客户端级别(而不是领域)的用户和管理员,并分配给不同的用户。

  • JDK 17的
  • 钥匙斗篷 22.0.0。
  • Spring Boot 3.1.1 (英语)

以下是以下工作解决方案:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class OAuth2ResourceServerSecurityConfiguration {
    @Value("${keycloak.resource}")
    private String keycloakClientName;
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
        .authorizeHttpRequests((authorize) -> {
            authorize
            .anyRequest().authenticated();
        })
        .oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer -> httpSecurityOAuth2ResourceServerConfigurer
            .jwt(jwtConfigurer -> {
                jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());
            })
        );

        return httpSecurity.build();
    }
    private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());

        return jwtAuthenticationConverter;
    }

    private class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
        private Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        @Override
        public Collection<GrantedAuthority> convert(Jwt jwt) {
            final Map<String, Object> resourceAccess = (Map<String, Object>) jwt.getClaims().get("resource_access");

            if (resourceAccess != null) {
                final Map<String, Object> clientAccess = (Map<String, Object>) resourceAccess.get(OAuth2ResourceServerSecurityConfiguration.this.keycloakClientName);

                if (clientAccess != null) {
                    grantedAuthorities = ((List<String>) clientAccess.get("roles")).stream()
                            .map(roleName -> "ROLE_" + roleName) // Prefix to map to a Spring Security "role"
                            .map(SimpleGrantedAuthority::new)
                            .collect(Collectors.toList());
                }
            }

            return grantedAuthorities;
        }
    }
}

属性中的Keycloak配置:

keycloak:
  authServerUrl: http://<your_keycloak_host>:8989
  realm: <your_realm>
  resource: <your_client>
  useResourceRoleMappings: true
  cors: true
  corsMaxAge: 1000
  corsAllowedMethods: POST, PUT, DELETE, GET
  sslRequired: none
  bearerOnly: true
  publicClient: true
  principalAttribute: preferred_username
  credentials:
    secret: '{cipher}<your_encrypted_secret>'

和测试控制器:

@RestController
@RequestMapping("/api/v1/test")
public class TestController {
    @GetMapping("/")
    public String allAccess() {
        return "Public content";
    }

    @GetMapping("/endpoint1")
    @PreAuthorize("hasRole('user')")
    public String endpoint1() {
        return "User board";
    }

    @GetMapping("/endpoint2")
    @PreAuthorize("hasRole('administrator')")
    public String endpoint2() {
        return "Administrator board";
    }
}

评论

2赞 ch4mp 7/18/2023
这基本上只是已接受答案的一部分(资源服务器的一半),大多数属性没有被使用(只有),并且您的代码似乎需要您的代码片段中缺少的属性。此外,在资源服务器过滤器链中,您应该禁用会话(和 CSRF 保护),并确保将 401 返回给未经授权的请求。简而言之,如果你阅读了答案(也许点击了链接),你就会节省一个周末。keycloakkeycloak.resourcespring.security.oauth2.resourceserver