PatPanda
PatPanda

Reputation: 5042

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

I am facing an issue with Spring Security + Spring Webflux + X509 Client certificate validation. May I get some assistance please?

With the minimal working code below:

@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

I was expecting this to work, i.e. that I can do the X509 certificate validation. However, I am facing this rather strange exception I am not understanding

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]

Question: What does this "sun.security.x509.X509CertImpl and java.lang.String are in module java.base of loader 'bootstrap'" means, how did I end up with this issue, and how to fix it please?

Thank you

Upvotes: 0

Views: 1412

Answers (1)

Rob Winch
Rob Winch

Reputation: 21720

The problem is that the AbstractUserDetailsReactiveAuthenticationManager is intended to validate a username/password not a X509 certificate, so it is trying to cast the X509 credential to a String to validate it like a password.

Instead, remove the ReactiveAuthenticationManager Bean and allow the default ReactivePreAuthenticatedAuthenticationManager to be used which is automatically created from the ReactiveUserDetailsService bean. The SubjectDnX509PrincipalExtractor is the default, so there is no need to explicitly provide it, but if you need to customize it just expose it as a Bean and it will be used by default.

@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());
    }

}

You can also find a complete sample at https://github.com/spring-projects/spring-security-samples/blob/592e4f89d2819f255340518a2c91b7e6eab6ed3e/reactive/webflux/java/authentication/x509/src/main/java/example/WebfluxX509Application.java

Upvotes: 2

Related Questions