OTUser
OTUser

Reputation: 3848

Spring Boot: Disable Client Auth for specific URL

I have the below config in my application.yml

server:
  #address:
  port: 8443
  sessionTimeout: 30
  ssl:
    client-auth: need
    key-store: keyStore.jks
    key-store-password: 12345
    key-password: datacert

    protocol: TLS
    trust-store: truststore.jks
    trust-store-password: 12345

Since I have client-auth: need, my application is prompting for certificate for all the URL's, is there anyway I can bypass client auth for some specific URL's like /info or \health URLs?

Upvotes: 9

Views: 4919

Answers (2)

Errong Leng
Errong Leng

Reputation: 1

well, there is a way, set server.ssl.client-auth to 'want' rather than 'need'. and customize a X509 authenticte filter to by pass for a few endpoints and stil require client cert for certain endpoints.

some key points codes in a spring webservice app.

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        CustomX509AuthenticationFilter x509AuthenticationFilter = new CustomX509AuthenticationFilter();
        http
                .authorizeRequests(authorizeRequests -> {
                    try {
                        authorizeRequests.requestMatchers(
                                new AntPathRequestMatcher("/ws/*.wsdl", "GET"),
                                new AntPathRequestMatcher("/ws/*.wsdl", "GET"),
                                new AntPathRequestMatcher("/ws/*.xsd", "GET"))
                                .permitAll().and().x509(AbstractHttpConfigurer::disable);
                        authorizeRequests.requestMatchers(
                                new AntPathRequestMatcher("/ws/op1", "POST"),
                                new AntPathRequestMatcher("/ws/op2", "POST")
                        ).permitAll().and().x509(x -> x.subjectPrincipalRegex(subjectPrincipalRegex)
                                .x509AuthenticationFilter(x509AuthenticationFilter)
                                .userDetailsService(username -> {
                                    log.info("username in client cert is : {}, {}", username, subjectPrincipalRegex);
                                    if (!username.equalsIgnoreCase(userNameInClientCert)) {
                                        throw new RequestRejectedException("no match username found from the client certificate in request.");
                                    }
                                    // TODO... check username against ldap or your username manager service
                                    // should throw new RequestRejectedException in case required username not found
                                    return new UserDetails() {
                                        @Override
                                        public Collection<? extends GrantedAuthority> getAuthorities() {
                                            List result = new ArrayList<>();
                                            result.add(new GrantedAuthority() {
                                                @Override
                                                public String getAuthority() {
                                                    return "read";
                                                }
                                            });
                                            return result;
                                        }

                                        @Override
                                        public String getPassword() {
                                            return null;
                                        }

                                        @Override
                                        public String getUsername() {
                                            return username;
                                        }

                                        @Override
                                        public boolean isAccountNonExpired() {
                                            return true;
                                        }

                                        @Override
                                        public boolean isAccountNonLocked() {
                                            return true;
                                        }

                                        @Override
                                        public boolean isCredentialsNonExpired() {
                                            return true;
                                        }

                                        @Override
                                        public boolean isEnabled() {
                                            return true;
                                        }
                                    };
                                })).csrf(c -> c.disable());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
        SecurityFilterChain sc = http.build();
        x509AuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        return sc;
    }
}              

the custom filter implemenation

public class CustomX509AuthenticationFilter extends X509AuthenticationFilter {
    private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
    public CustomX509AuthenticationFilter() {}

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        X509Certificate cert = extractClientCertificate(request);
        return (cert != null) ? this.principalExtractor.extractPrincipal(cert) : null;
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return extractClientCertificate(request);
    }

    private X509Certificate extractClientCertificate(HttpServletRequest request) {
        // for get method, not use the client cert even if it existed
        if (HttpMethod.GET.matches(request.getMethod())) {
            return null;
        }
        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("jakarta.servlet.request.X509Certificate");
        if (certs != null && certs.length > 0) {
            this.logger.debug(LogMessage.format("X.509 client authentication certificate:%s", certs[0]));
            return certs[0];
        }
        this.logger.debug("No client certificate found in request.");
        // for soap request we should reject the request if no client cert found
        if (HttpMethod.POST.matches(request.getMethod())) {
            throw new RequestRejectedException("No client certificate found in request.");
        }
        return null;
    }

    public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
        this.principalExtractor = principalExtractor;
    }

}

check more details in this post on my personal blog

Upvotes: 0

A Mahajan
A Mahajan

Reputation: 21

Spring boot does not allow to mix http and https. It is also not suggested to mix the URL's with insecure pages.

If you need only certain URL's to be secure please implement a custom authentication as by default the SSL handshake will happen for each and every request.

You can refer these discussions: How set up Spring Boot to run HTTPS / HTTP ports

For instance the request will reach to the server only after it is authenticated.

You may try to create and capture the request using Filters but I doubt that you will get the request at server before it is authenticated by web server.

e.g. Try adding java Filter and capture the required URL's to filter secure and non secure. (Filter is configurable under web.xml or as annotation). Other option is to try Spring Security filter chain proxy.

Upvotes: 2

Related Questions