问题
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.
回答1:
You could configure clientAuth=want
, see Apache Tomcat 8 Configuration Reference:
Set to
true
if you want the SSL stack to require a valid certificate chain from the client before accepting a connection. Set towant
if you want the SSL stack to request a client Certificate, but not fail if one isn't presented. Afalse
value (which is the default) will not require a certificate chain unless the client requests a resource protected by a security constraint that usesCLIENT-CERT
authentication.
and then read the client certificate with Spring Security - X.509 Authentication:
You can also use SSL with "mutual authentication"; the server will then request a valid certificate from the client as part of the SSL handshake. The server will authenticate the client by checking that its certificate is signed by an acceptable authority. If a valid certificate has been provided, it can be obtained through the servlet API in an application. Spring Security X.509 module extracts the certificate using a filter. It maps the certificate to an application user and loads that user’s set of granted authorities for use with the standard Spring Security infrastructure.
and
clientAuth
can also be set towant
if you still want SSL connections to succeed even if the client doesn’t provide a certificate. Clients which don’t present a certificate won’t be able to access any objects secured by Spring Security unless you use a non-X.509 authentication mechanism, such as form authentication.
回答2:
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.
来源:https://stackoverflow.com/questions/33808603/implementing-2-way-ssl-using-spring-boot