ClassNotFoundException:org.springframework.security.oauth2.server.resource.web.BearerTokenResolver

问题描述 投票:0回答:2

我在使用 Spring Security oauth2 与 Keycloak 进行验证期间遇到相关错误。 我不认为我的依赖项中缺少它。

错误

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'filterChain' defined in class path resource [com/framework/security/SecurityConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'filterChain' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/security/oauth2/server/resource/web/BearerTokenResolver
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-5.3.23.jar:5.3.23]
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.23.jar:5.3.23]
        at 
    Caused by: java.lang.ClassNotFoundException: org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
        at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[?:?]

我的安全课

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/login/**").hasRole("role-name").anyRequest().authenticated().and().oauth2ResourceServer().jwt();
        return http.build();
    }
}

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
    </dependencies>

application.yml

security:
    oauth2:
      resource-server:
        jwt:
          issuer-uri: http://keycloak-url/realms/realm-id
          jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
      client:
        registration:
          client-ui:
            provider: keycloak
            client-id: client-ui
            client-secret: abcdefgh
            authorization-grant-type: authorization_code
            scope: openid
        provider:
          client-ui:
            authorization-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/auth
            token-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/token
            user-info-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/userinfo
            jwk-set-uri: http://keycloak-url/realms/realm-id/protocol/openid-connect/certs
            user-name-attribute: preferred_username

我尝试进行多次编辑,但我找不到问题所在。我该如何解决它。

java spring spring-security keycloak spring-security-oauth2
2个回答
0
投票

首先,您应该确定您的应用主要是 OAuth2 客户端还是 OAuth2 资源服务器。在这里,您在同一个安全过滤器链中混合了客户端和资源服务器配置。那行不通的。我写了一个“OAuth2 essentials”部分作为 my tutorials 的介绍,这应该可以帮助您做出决定。

Servlet 资源服务器(REST API)

完整教程.

资源服务器不关心客户端关心的登录和 OAuth2 流程。它只关心请求是否被授权(具有有效的访问令牌),是否应该自省或解码此令牌,以及是否应该根据令牌声明授予对所请求资源的访问权限。

使用 Postman 或任何可以授权其请求并发送任何类型的请求(不仅是 GET,还有 POST、PUT 和 DELETE)的 OAuth2 客户端来尝试您正在运行的 API。

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
@SpringBootApplication
public class ResourceServerMinimalApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResourceServerMinimalApplication.class, args);
    }

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Bean
    SecurityFilterChain resourceServerSecurityFilterChain(
            HttpSecurity http,
            @Value("${resource-server.cors.allowed-origins:}#{T(java.util.Collections).emptyList()}") List<String> allowedOrigins)
            throws Exception {
        http.oauth2ResourceServer(oauth2 -> oauth2.jwt());
        http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        http.csrf(csrf -> csrf.disable());
        http.exceptionHandling(handeling -> handeling.authenticationEntryPoint((request, response, authException) -> {
            response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }));
        http.authorizeHttpRequests().anyRequest().authenticated();
        http.cors(cors -> {
            if (allowedOrigins.isEmpty()) {
                cors.disable();
            } else {
                cors.configurationSource(corsConfig(allowedOrigins));
            }
        });
        return http.build();
    }

    CorsConfigurationSource corsConfig(List<String> allowedOrigins) {
        final var source = new UrlBasedCorsConfigurationSource();

        final var configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(allowedOrigins);
        configuration.setAllowedMethods(List.of("*"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setExposedHeaders(List.of("*"));

        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @RestController
    @RequestMapping("/api/v1")
    public static class GreetingController {
        @GetMapping("/greet")
        public ResponseEntity<GreetingDto> getGreeting(Authentication auth) {
            return ResponseEntity.ok(new GreetingDto("Hello %s!".formatted(auth.getName())));
        }

        static record GreetingDto(String message) {
        }
    }
}
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://keycloak-url/realms/realm-id

resource-server:
  cors:
    allowed-origins:
    - http://localhost:4200

Servlet 客户端(提供 Thymeleaf 模板)

完整教程.

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
@SpringBootApplication
public class ClientMinimalApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClientMinimalApplication.class, args);
    }

    @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    @Bean
    SecurityFilterChain clientSecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
        http.oauth2Login();
        final var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
        http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));
        http.cors().disable();
        // sessions are required (enabled by default)
        // CSRF protection is required (enabled by default) because security is based on sessions
        http.authorizeHttpRequests().requestMatchers("/login/**", "/oauth2/**").permitAll() // this is required for unauthenticated users to login
                .anyRequest().authenticated();
        return http.build();
    }

    @Controller
    @RequestMapping
    public static class UiController {
        @GetMapping("/")
        public RedirectView getIndex() throws URISyntaxException {
            return new RedirectView("/ui/greet");
        }

        @GetMapping("/ui/greet")
        public String getGreeting(Authentication auth, Model model) throws URISyntaxException {
            model.addAttribute("message", "Hello %s!".formatted(auth.getName()));
            return "greet";
        }
    }
}
spring:
  security:
    oauth2:
      client:
        provider:
          keycloak-realm-id:
            issuer-uri: http://keycloak-url/realms/realm-id
        registration:
          keycloak-confidential-user:
            authorization-grant-type: authorization_code
            client-name: a local Keycloak instance
            client-id: client-ui
            client-secret: ${keycloak-secret}
            provider: keycloak-realm-id
            scope: openid,profile,email,offline_access

/src/main/resources/templates/greet.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <title>Greetings!</title>
</head>

<body>
    <h1 th:utext="${message}">..!..</h1>
</body>

结合客户端和资源服务器

如果您的应用程序公开了供 OAuth2 客户端使用的 REST API 和供浏览器查询的其他一些资源(没有 Angular、React 或 Vue 等框架的 OAuth2 客户端库的帮助),并且仅在在这种情况下,定义上面的两个不同的过滤器链,并在第一个过滤器链上添加一个

securityMatcher
,以将其限制在它应该保护的路由上。

在上面提供的示例中,具有最低顺序的安全过滤器链是资源服务器之一。添加像

http.securityMatcher("/api/**");
这样的东西就可以了:所有其他路由都将使用客户端过滤器链进行保护,该过滤器链将在之后尝试并拦截所有尚未被资源服务器过滤器链拦截的请求。

完整教程.


-1
投票

也许,您在

pom.xml

中缺少 oauth2-resource-server 依赖项
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-oauth2-resource-server</artifactId>
  <version>6.0.2</version>
</dependency>

这实际上会自动附加所需的

DefaultBearerTokenResolver
Bean,并且应该开箱即用。

© www.soinside.com 2019 - 2024. All rights reserved.