Reputation: 3848
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
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
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