I'm creating some restful web services and am using Spring-Boot to create an embedded tomcat container.
One of the requirements is that this implements 2 way SSL. I've been looking at the HttpSecurity object and can get it to only run the webservices over an SSL channel using this:-
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("CONFIGURED");
http
// ...
.requiresChannel()
.anyRequest().requiresSecure();
}
What I can't seem to find is a way of making the webservice only accessible to applications providing a valid client cert.
I have only a basic knowledge of SSL so even a general pointer in the right direction would be appreciated.
The server this is being deployed onto will have a mix of applications - this is the only one that needs to be locked down with 2-way SSL. What I'm really looking for is a way of locking down a single application to only accept client certificates.
In Two-Way SSL authentication, the client and server need to authenticate and validate each others identities. The authentication message exchange between client and server is called an SSL handshake, and it includes the following steps: A client requests access to a protected resource.
I came across a similar problem, and thought I’d share the solution I came with.
First, you need to understand that the SSL certificate authentication will be handled on your web server’s side (cfr. dur’s explanation, with the “clientAuth=want” setting). Then, your web app must be configured in order to handle the provided (and allowed) certificate, map it to a user etc.
The slight difference I have with you is that I’m packaging my spring boot application into a WAR archive, which is then deployed on an existing Tomcat application server.
My Tomcat’s server.xml configuration file defines an HTTPS connector as follows:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
keystoreFile="/opt/tomcat/conf/key-stores/ssl-keystore.jks"
keystorePass=“some-complex-password“
clientAuth="want" sslProtocol="TLS"
truststoreFile="/opt/tomcat/conf/trust-stores/ssl-truststore.jks"
truststorePass=“some-other-complex-password” />
Small comment to avoid any confusion: keystoreFile contains the certificate/private key pair used for SSL (only), while truststoreFile contains the allowed CA certificates for client SSL authentication (note that you could also add the client certificates directly into that trust-store).
If you're using an embedded tomcat container with your spring boot application, you should be able to configure these settings in your application’s properties file, using the following property key/values:
server.ssl.key-store=/opt/tomcat/conf/key-stores/ssl-keystore.jks
server.ssl.key-store-password=some-complex-password
server.ssl.trust-store=/opt/tomcat/conf/trust-stores/ssl-truststore.jks
server.ssl.trust-store-password=some-other-complex-password
server.ssl.client-auth=want
Then, on my web app, I declare a specific SSL configuration as follows:
@Configuration
@EnableWebSecurity
//In order to use @PreAuthorise() annotations later on...
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SSLAuthConfiguration extends WebSecurityConfigurerAdapter {
@Value("${allowed.user}")
private String ALLOWED_USER;
@Value("${server.ssl.client.regex}")
private String CN_REGEX;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure (final HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/url-path-to-protect").authenticated() //Specify the URL path(s) requiring authentication...
.and()
.x509() //... and that x509 authentication is enabled
.subjectPrincipalRegex(CN_REGEX)
.userDetailsService(userDetailsService);
}
@Autowired
//Simplified case, where the application has only one user...
public void configureGlobal (final AuthenticationManagerBuilder auth) throws Exception {
//... whose username is defined in the application's properties.
auth
.inMemoryAuthentication()
.withUser(ALLOWED_USER).password("").roles("SSL_USER");
}
}
I then need to declare the UserDetailsService bean (e.g. in my Application’s main class):
@Value("${allowed.user}")
private String ALLOWED_USER;
@Bean
public UserDetailsService userDetailsService () {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
if (username.equals(ALLOWED_USER)) {
final User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_SSL_USER"));
return user;
}
return null;
}
};
}
And that’s it! I can then add @PreAuthorize(“hasRole(‘ROLE_SSL_USER’)”) annotations to the methods that I want to secure.
To sum things up a bit, the authentication flow will be as follows:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With