使用 Keycloak 保护 Spring Boot REST 服务时的 NullPointer

Posted

技术标签:

【中文标题】使用 Keycloak 保护 Spring Boot REST 服务时的 NullPointer【英文标题】:NullPointer when securing Spring Boot REST Service with Keycloak 【发布时间】:2021-02-04 04:36:36 【问题描述】:

我正在尝试按照文章 https://medium.com/devops-dudes/securing-spring-boot-rest-apis-with-keycloak-1d760b2004e 中的说明使用 Keycloak 保护 Spring Boot REST 服务。我将 keycloak 和 my-service 作为 docker 服务启动(请参阅下面的 docker-compose.yml)

那我先

curl -s -X POST http://localhost:8089/auth/realms/<realm>/protocol/openid-connect/token \
     --header "Content-Type: application/x-www-form-urlencoded" \
     --data-urlencode "grant_type=password" \
     --data-urlencode "client_id=<my-app>" \
     --data-urlencode "client_secret=<secret>" \
     --data-urlencode "username=<user>" \
     --data-urlencode "password=<password>" 
 

然后在将环境变量 $TOKEN 的值设置为上述调用返回的 access_token 后,尝试调用 my-service 上的端点:

curl -s --location --request POST http://localhost:8082/myendpoint --header "Authorization: Bearer $TOKEN" 

当我启动最后一个 curl 时,spring-boot 服务会引发 NullPointerException,如下面的堆栈跟踪所示。

spring-boot-starter-parent 版本=1.5.12.RELEASE Keycloak version=3.4.3:Final

以下是KeycloakSecurityConfig类:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter 

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 

        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);

    

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() 
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() 
        return new KeycloakSpringBootConfigResolver();
    


    @Override
    protected void configure(HttpSecurity http) throws Exception
    
        super.configure(http);
        http.authorizeRequests().anyRequest().permitAll();
        http.csrf().disable();
    


这是pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.RELEASE</version> 

        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>my.app</groupId>
    <artifactId>my-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <docker.image.prefix>my.app</docker.image.prefix>
    </properties>

    
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- spring boot actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- spring data rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency> 

        
        <!-- Keycloak -->
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>3.4.3.Final</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>$project.artifactId</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>$main.class</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>0.2.3</version>
                <configuration>
                    <imageName>$docker.image.prefix/$project.artifactId:$project.version</imageName>
                    <dockerDirectory>src/main/docker</dockerDirectory>
                    <resources>
                        <resource>
                            <directory>$project.build.directory</directory>
                            <include>$project.build.finalName.jar</include>
                        </resource>
                    </resources>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

</project>

这是docker-compose.yml 文件:

version: '3.1'

services:

  my-service-db:
    .
    .
    .
    
  my-service:
    image:  my.app/my-service:0.0.1-SNAPSHOT
    container_name: my-service
    ports:
      - 8082:8080
    links:
      - my-service-db
    environment:
      .
      .
      .
      # keycloak connector values
      KEYCLOAK_REALM: <realm>
      KEYCLOAK_AUTH_SERVER_URL: keycloak:8089/auth
      KEYCLOAK_SSL_REQUIRED: external
      KEYCLOAK_RESOURCE: <my-service>
      KEYCLOAK_CREDENTIAL_SECRET: <secret>

.
.
.

# KEYCLOAK INFRASTRUCTURE

  keycloak-db:
    image: mysql:5.6
    container_name:  keycloak-db
    command: "--innodb_use_native_aio=0"
    expose:
      - "3306"
    ports:
      - "3308:3306"
    volumes:
      - ./keycloak-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <password>
      MYSQL_ROOT_HOST: "%"
      MYSQL_DATABASE: keycloak
      MYSQL_USER: <user>
      MYSQL_PASSWORD: <password>

  keycloak:
    container_name: keycloak
    # restart: always
    image: jboss/keycloak:3.4.3.Final
    depends_on:
      - keycloak-db
    environment:
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: <password>
      MYSQL_DATABASE: keycloak
      MYSQL_USER: <user>
      MYSQL_PASSWORD: <password>
      # Workaround for container using legacy Docker links, resulting in
      # "WFLYCTL0211: Cannot resolve expression 'jdbc:mysql://$env.MYSQL_PORT_3306_TCP_ADDR:$env.MYSQL_PORT_3306_TCP_PORT
      # see: https://issues.redhat.com/browse/KEYCLOAK-3873?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel
      MYSQL_PORT_3306_TCP_ADDR: keycloak-db
      MYSQL_PORT_3306_TCP_PORT: "3306"
    ports:
      - 8089:8080
      # - 8443:8443
      # - 9990:9990
    volumes:
      - ./keycloak-data:/data
    

最后,这是错误堆栈跟踪:

2020-10-21 09:00:31.614 ERROR 1 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.NullPointerException: null
        at java.net.URI$Parser.parse(URI.java:3042) ~[na:1.8.0_111]
        at java.net.URI.<init>(URI.java:588) ~[na:1.8.0_111]
        at java.net.URI.create(URI.java:850) ~[na:1.8.0_111]
        at org.apache.http.client.methods.HttpGet.<init>(HttpGet.java:66) ~[httpclient-4.5.5.jar!/:4.5.5]
        at org.keycloak.adapters.rotation.JWKPublicKeyLocator.sendRequest(JWKPublicKeyLocator.java:97) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.rotation.JWKPublicKeyLocator.getPublicKey(JWKPublicKeyLocator.java:63) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.rotation.AdapterRSATokenVerifier.getPublicKey(AdapterRSATokenVerifier.java:44) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.rotation.AdapterRSATokenVerifier.verifyToken(AdapterRSATokenVerifier.java:55) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.rotation.AdapterRSATokenVerifier.verifyToken(AdapterRSATokenVerifier.java:37) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.BearerTokenRequestAuthenticator.authenticateToken(BearerTokenRequestAuthenticator.java:99) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.BearerTokenRequestAuthenticator.authenticate(BearerTokenRequestAuthenticator.java:84) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.RequestAuthenticator.authenticate(RequestAuthenticator.java:68) ~[keycloak-adapter-core-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:147) ~[keycloak-spring-security-adapter-3.4.3.Final.jar!/:3.4.3.Final]
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:84) ~[keycloak-spring-security-adapter-3.4.3.Final.jar!/:3.4.3.Final]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177) ~[spring-security-web-4.2.5.RELEASE.jar!/:4.2.5.RELEASE]
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.5.12.RELEASE.jar!/:1.5.12.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.16.RELEASE.jar!/:4.3.16.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) [spring-boot-container-bundle-3.4.3.Final.jar!/:3.4.3.Final]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) [spring-boot-container-bundle-3.4.3.Final.jar!/:3.4.3.Final]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_111]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_111]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.29.jar!/:8.5.29]
        at java.lang.Thread.run(Thread.java:745) [na:1.8.0_111]

【问题讨论】:

【参考方案1】:

我遇到了同样的问题,这与后端服务器无法访问 keycloack 服务器有关。 我的后端服务器的配置:

keycloak:
  realm: myrealm
  auth-server-url: http://192.168.102.12/auth/

当地址可用时(你可以检查 telnet)一切都开始工作了。

telnet 192.168.102.12 80

我在日志中遇到了与您相同的错误,但您必须在日志中查看上方:

2020-11-27 16:43:38.879  WARN 14688 --- [nio-7113-exec-1] o.keycloak.adapters.KeycloakDeployment   : Failed to load URLs from http://192.168.102.12/auth/realms/myrealm/.well-known/openid-configuration

java.net.ConnectException: Connection refused
        at java.base/java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:na]
        at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399) ~[na:na]

因为这个日志在下面:

2020-11-27 16:43:38.885 ERROR 14688 --- [nio-7113-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/mybackendapp] threw exception

java.lang.NullPointerException: null
        at java.base/java.net.URI$Parser.parse(URI.java:3104) ~[na:na]
        at java.base/java.net.URI.<init>(URI.java:600) ~[na:na]
        at java.base/java.net.URI.create(URI.java:881) ~[na:na]
        at org.apache.http.client.methods.HttpGet.<init>(HttpGet.java:66) ~[httpclient-4.5.12.jar!/:4.5.12]
        at org.keycloak.adapters.rotation.JWKPublicKeyLocator.sendRequest(JWKPublicKeyLocator.java:97) ~[keycloak-adapter-core-10.0.1.jar!/:10.0.1]
        at org.keycloak.adapters.rotation.JWKPublicKeyLocator.getPublicKey(JWKPublicKeyLocator.java:63) ~[keycloak-adapter-core-10.0.1.jar!/:10.0.1]

【讨论】:

以上是关于使用 Keycloak 保护 Spring Boot REST 服务时的 NullPointer的主要内容,如果未能解决你的问题,请参考以下文章

使用 Keycloak 保护 Spring Boot REST 服务时的 NullPointer

需要使用 Keycloak 和 oAuth2 保护普通 Spring REST API 的示例

如何使用 Keycloak 保护 Angular 8 前端和使用网关、eureka 的 Java Spring Cloud 微服务后端

通过 Keycloak 保护单个资源免受 Spring Boot 的影响

仅使用承载保护 Spring Cloud Gateway

在 Spring Boot 中扩展 Keycloak 令牌