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:
- User provides SSL certificate ;
- Tomcat validates against its trust-store ;
- The custom WebSecurityConfigurerAdapter retrieves a “username” from the certificate’s CN ;
- The application authenticates the user associated to the retrieved username ;
- At method level, if annotated with @PreAuthorize("hasRole('SSL_USER')"), the application will check whether the user has the required role.