Spring Webflux + Spring X509 证书验证问题的安全性:x509.X509CertImpl 无法转换为 class java.lang.String

Spring Webflux + Spring Security for X509 certificate validation issue: x509.X509CertImpl cannot be cast to class java.lang.String

我遇到了 Spring 安全性 + Spring Webflux + X509 客户端证书验证的问题。 我可以得到一些帮助吗?

使用下面的最小工作代码:

@RestController
@SpringBootApplication
public class X509AuthenticationServer {

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

    @RequestMapping(value = "/test")
    public Mono<String> test() {
        return Mono.just("\n test \n");
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity, UserDetailsRepositoryReactiveAuthenticationManager authenticationManager, X509PrincipalExtractor principalExtractor) {
        return serverHttpSecurity.authorizeExchange().anyExchange().authenticated().and().x509().principalExtractor(principalExtractor).authenticationManager(authenticationManager).and().build();
    }

    @Bean
    public X509PrincipalExtractor principalExtractor() {
        return new SubjectDnX509PrincipalExtractor();
    }

    @Bean
    public UserDetailsRepositoryReactiveAuthenticationManager authenticationManager(ReactiveUserDetailsService reactiveUserDetailsService) {
        return new UserDetailsRepositoryReactiveAuthenticationManager(reactiveUserDetailsService);
    }

    @Bean
    public MapReactiveUserDetailsService mapReactiveUserDetailsService() {
        return new MapReactiveUserDetailsService(User.withUsername("USER").authorities(new SimpleGrantedAuthority("USER")).password("").build());
    }

}
server.ssl.key-store=/keystore.jks
server.ssl.key-store-password=123456
server.ssl.key-alias=localhost
server.ssl.key-password=123456
server.ssl.enabled=true
server.ssl.trust-store=/truststore.jks
server.ssl.trust-store-password=123456
server.ssl.client-auth=need
server.port=8443
spring.security.user.name=test
spring.security.user.password=test

logging.level.root=DEBUG

我原以为这会起作用,即我可以进行 X509 证书验证。 但是,我正面临这个我不理解的相当奇怪的异常

2020-10-15 20:04:57.773 DEBUG [,9dfd3873894ac7fe,9dfd3873894ac7fe,true] 8646 --- [ctor-http-nio-3] .w.a.p.x.SubjectDnX509PrincipalExtractor : Subject DN is 'CN=(I see the correct CN), OU=zzzzzz, OU=xxxxxx, O=cccccc, C=US'
2020-10-15 20:04:57.773 DEBUG [,9dfd3873894ac7fe,9dfd3873894ac7fe,true] 8646 --- [ctor-http-nio-3] .w.a.p.x.SubjectDnX509PrincipalExtractor : Extracted Principal name is(I see the correct CN)
2020-10-15 20:04:57.785 DEBUG [,9dfd3873894ac7fe,9dfd3873894ac7fe,true] 8646 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [a8f68710/1-1] Resolved [ClassCastException: class sun.security.x509.X509CertImpl cannot be cast to class java.lang.String (sun.security.x509.X509CertImpl and java.lang.String are in module java.base of loader 'bootstrap')] for HTTP GET /test
2020-10-15 20:04:57.786 ERROR [,9dfd3873894ac7fe,9dfd3873894ac7fe,true] 8646 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [a8f68710/1-1]  500 Server Error for HTTP GET "/test

java.lang.ClassCastException: class sun.security.x509.X509CertImpl cannot be cast to class java.lang.String (sun.security.x509.X509CertImpl and java.lang.String are in module java.base of loader 'bootstrap')
    at org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager.authenticate(AbstractUserDetailsReactiveAuthenticationManager.java:99) ~[spring-security-core-5.3.4.RELEASE.jar:5.3.4.RELEASE]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]

问题:“sun.security.x509.X509CertImpl and java.lang.String are in module java.base of loader 'bootstrap'”是什么意思,我是怎么结束的这个问题,请问如何解决?

谢谢

问题是 AbstractUserDetailsReactiveAuthenticationManager 旨在验证 username/password 而不是 X509 证书,因此它试图将 X509 凭证转换为字符串以像验证密码一样验证它。

相反,删除 ReactiveAuthenticationManager Bean 并允许使用从 ReactiveUserDetailsService bean 自动创建的默认值 ReactivePreAuthenticatedAuthenticationManagerSubjectDnX509PrincipalExtractor 是默认的,所以不需要显式提供,但是如果你需要自定义它,只需将它作为一个 Bean 公开,它会被默认使用。

@RestController
@SpringBootApplication
public class X509AuthenticationServer {

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

    @RequestMapping(value = "/test")
    public Mono<String> test() {
        return Mono.just("\n test \n");
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // @formatter:off
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .x509();
        // @formatter:on
        return http.build();
    }

    @Bean
    public MapReactiveUserDetailsService mapReactiveUserDetailsService() {
        return new MapReactiveUserDetailsService(User.withUsername("USER").authorities("USER").password("").build());
    }

}

您还可以在 https://github.com/spring-projects/spring-security-samples/blob/592e4f89d2819f255340518a2c91b7e6eab6ed3e/reactive/webflux/java/authentication/x509/src/main/java/example/WebfluxX509Application.java

找到完整的样本