Autenticazione X.509 in Spring Security

1. Panoramica

In questo articolo, ci concentreremo sui principali casi d'uso per l'autenticazione del certificato X.509: verificare l'identità di un peer di comunicazione quando si utilizza il protocollo HTTPS (HTTP su SSL).

In poche parole: mentre viene stabilita una connessione sicura, il client verifica il server in base al suo certificato (emesso da un'autorità di certificazione attendibile).

Ma oltre a ciò, X.509 in Spring Security può essere utilizzato per verificare l'identità di un client dal server durante la connessione. Questa è chiamata "autenticazione reciproca" e vedremo come viene eseguita anche qui.

Infine, toccheremo quando ha senso utilizzare questo tipo di autenticazione .

Per dimostrare la verifica del server, creeremo una semplice applicazione web e installeremo un'autorità di certificazione personalizzata in un browser.

Inoltre, per l'autenticazione reciproca , creeremo un certificato client e modificheremo il nostro server per consentire solo i client verificati.

Si consiglia vivamente di seguire il tutorial passo passo e creare i certificati, nonché il keystore e il truststore, da soli, secondo le istruzioni presentate nelle sezioni seguenti. Tuttavia, tutti i file pronti per l'uso possono essere trovati nel nostro repository GitHub.

2. CA radice autofirmata

Per poter firmare i nostri certificati lato server e lato client, dobbiamo prima creare il nostro certificato CA root autofirmato. In questo modo agiremo come la nostra autorità di certificazione .

A questo scopo useremo la libreria openssl, quindi dobbiamo averla installata prima di seguire il passaggio successivo.

Creiamo ora il certificato CA:

openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -out rootCA.crt

Quando eseguiamo il comando precedente, dobbiamo fornire la password per la nostra chiave privata. Ai fini di questo tutorial, usiamo changeit come passphrase.

Inoltre, dobbiamo inserire informazioni che formano un cosiddetto nome distinto . Qui forniamo solo il CN (nome comune) - Baeldung.com - e lasciamo vuote le altre parti.

3. Keystore

Requisito facoltativo : per utilizzare chiavi crittograficamente complesse insieme a funzioni di crittografia e decrittografia, avremo bisogno dei " file di criteri di giurisdizione di Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction " installati nella nostra JVM .

Questi possono essere scaricati ad esempio da Oracle (seguire le istruzioni di installazione incluse nel download). Alcune distribuzioni Linux forniscono anche un pacchetto installabile tramite i loro gestori di pacchetti.

Un keystore è un repository che la nostra applicazione Spring Boot utilizzerà per conservare la chiave privata e il certificato del nostro server. In altre parole, la nostra applicazione utilizzerà il keystore per fornire il certificato ai client durante l'handshake SSL.

In questo tutorial, utilizziamo il formato Java Key-Store (JKS) e uno strumento da riga di comando keytool.

3.1. Certificato lato server

Per implementare l'autenticazione X.509 lato server nella nostra applicazione Spring Boot, dobbiamo prima creare un certificato lato server.

Cominciamo con la creazione di una cosiddetta richiesta di firma del certificato (CSR):

openssl req -new -newkey rsa:4096 -keyout localhost.key –out localhost.csr

Allo stesso modo, come per il certificato CA, dobbiamo fornire la password per la chiave privata. Inoltre, usiamo localhost come nome comune (CN).

Prima di procedere, dobbiamo creare un file di configurazione - localhost.ext . Memorizzerà alcuni parametri aggiuntivi necessari durante la firma del certificato.

authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE subjectAltName = @alt_names [alt_names] DNS.1 = localhost

Un file pronto per l'uso è anche disponibile qui.

Ora è il momento di firmare la richiesta con il nostro certificato rootCA.crt e la sua chiave privata :

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile localhost.ext

Nota che dobbiamo fornire la stessa password che abbiamo utilizzato quando abbiamo creato il nostro certificato CA.

In questa fase, abbiamo finalmente un certificato localhost.crt pronto per l'uso firmato dalla nostra autorità di certificazione.

Per stampare i dettagli del nostro certificato in un formato leggibile dall'uomo possiamo utilizzare il seguente comando:

openssl x509 -in localhost.crt -text

3.2. Importa nel keystore

In questa sezione vedremo come importare il certificato firmato e la corrispondente chiave privata nel file keystore.jks .

Useremo l'archivio PKCS 12, per impacchettare la chiave privata del nostro server insieme al certificato firmato. Quindi lo importeremo nel keystore.jks appena creato .

Possiamo usare il seguente comando per creare un file .p12 :

openssl pkcs12 -export -out localhost.p12 -name "localhost" -inkey localhost.key -in localhost.crt

Quindi ora abbiamo localhost.key e localhost.crt raggruppati nel singolo file localhost.p12 .

Usiamo ora keytool per creare un repository keystore.jks e importare il file localhost.p12 con un singolo comando :

keytool -importkeystore -srckeystore localhost.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS

In questa fase, abbiamo tutto a posto per la parte di autenticazione del server. Procediamo con la nostra configurazione dell'applicazione Spring Boot.

4. Applicazione di esempio

Our SSL secured server project consists of a @SpringBootApplication annotated application class (which is a kind of @Configuration), an application.properties configuration file and a very simple MVC-style front-end.

All, the application has to do, is to present an HTML page with a “Hello {User}!” message. This way we can inspect the server certificate in a browser to make sure, that the connection is verified and secured.

4.1. Maven Dependencies

First, we create a new Maven project with three Spring Boot Starter bundles included:

 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf 

For reference: we can find the bundles on Maven Central (security, web, thymeleaf).

4.2. Spring Boot Application

As the next step, we create the main application class and the user-controller:

@SpringBootApplication public class X509AuthenticationServer { public static void main(String[] args) { SpringApplication.run(X509AuthenticationServer.class, args); } } @Controller public class UserController { @RequestMapping(value = "/user") public String user(Model model, Principal principal) { UserDetails currentUser = (UserDetails) ((Authentication) principal).getPrincipal(); model.addAttribute("username", currentUser.getUsername()); return "user"; } }

Now, we tell the application where to find our keystore.jks and how to access it. We set SSL to an “enabled” status and change the standard listening port to indicate a secured connection.

Additionally, we configure some user-details for accessing our server via Basic Authentication:

server.ssl.key-store=../store/keystore.jks server.ssl.key-store-password=${PASSWORD} server.ssl.key-alias=localhost server.ssl.key-password=${PASSWORD} server.ssl.enabled=true server.port=8443 spring.security.user.name=Admin spring.security.user.password=admin

This will be the HTML template, located at the resources/templates folder:

 X.509 Authentication Demo 

Hello !

4.3. Root CA Installation

Before we finish this section and look at the site, we need to install our generated root certificate authority as a trusted certificate in a browser.

An exemplary installation of our certificate authority for Mozilla Firefox would look like follows:

  1. Type about:preferences in the address bar
  2. Open Advanced -> Certificates -> View Certificates -> Authorities
  3. Click on Import
  4. Locate the Baeldung tutorials folder and its subfolder spring-security-x509/keystore
  5. Select the rootCA.crt file and click OK
  6. Choose “Trust this CA to identify websites” and click OK

Note: If you don't want to add our certificate authority to the list of trusted authorities, you'll later have the option to make an exception and show the website tough, even when it is mentioned as insecure. But then you'll see a ‘yellow exclamation mark' symbol in the address bar, indicating the insecure connection!

Afterward, we will navigate to the spring-security-x509-basic-auth module and run:

mvn spring-boot:run

Finally, we hit //localhost:8443/user, enter our user credentials from the application.properties and should see a “Hello Admin!” message. Now we're able to inspect the connection status by clicking the “green lock” symbol in the address bar, and it should be a secured connection.

5. Mutual Authentication

In the previous section, we presented how to implement the most common SSL authentication schema – server-side authentication. This means, only a server authenticated itself to clients.

In this section, we'll describe how to add the other part of the authentication – client-side authentication. This way, only clients with valid certificates signed by the authority that our server trusts, can access our secured website.

But before we continue, let's see what are the pros and cons of using the mutual SSL authentication.

Pros:

  • The private key of an X.509 client certificate is stronger than any user-defined password. But it has to be kept secret!
  • With a certificate, the identity of a client is well-known and easy to verify.
  • No more forgotten passwords!

Cons:

  • We need to create a certificate for each new client.
  • The client's certificate has to be installed in a client application. In fact: X.509 client authentication is device-dependent, which makes it impossible to use this kind of authentication in public areas, for example in an internet-café.
  • There must be a mechanism to revoke compromised client certificates.
  • We must maintain the clients' certificates. This can easily become costly.

5.1. Truststore

A trustsore in some way is the opposite of a keystore. It holds the certificates of the external entities that we trust.

In our case, it's enough to keep the root CA certificate in the truststore.

Let's see how to create a truststore.jks file and import the rootCA.crt using keytool:

keytool -import -trustcacerts -noprompt -alias ca -ext san=dns:localhost,ip:127.0.0.1 -file rootCA.crt -keystore truststore.jks

Note, we need to provide the password for the newly created trusstore.jks. Here, we again used the changeit passphrase.

That's it, we've imported our own CA certificate, and the truststore is ready to be used.

5.2. Spring Security Configuration

To continue, we are modifying our X509AuthenticationServer to extend from WebSecurityConfigurerAdapter and override one of the provided configure methods. Here we configure the x.509 mechanism to parse the Common Name (CN) field of a certificate for extracting usernames.

With this extracted usernames, Spring Security is looking up in a provided UserDetailsService for matching users. So we also implement this service interface containing one demo user.

Tip: In production environments, this UserDetailsService can load its users for example from a JDBC Datasource.

You have to notice that we annotate our class with @EnableWebSecurity and @EnableGlobalMethodSecurity with enabled pre-/post-authorization.

With the latter we can annotate our resources with @PreAuthorize and @PostAuthorize for fine-grained access control:

@SpringBootApplication @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class X509AuthenticationServer extends WebSecurityConfigurerAdapter { ... @Override protected void configure(HttpSecurity http) throws Exception $)") .userDetailsService(userDetailsService());  @Bean public UserDetailsService userDetailsService() { return new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) { if (username.equals("Bob")) { return new User(username, "", AuthorityUtils .commaSeparatedStringToAuthorityList("ROLE_USER")); } throw new UsernameNotFoundException("User not found!"); } }; } }

As said previously, we are now able to use Expression-Based Access Control in our controller. More specifically, our authorization annotations are respected because of the @EnableGlobalMethodSecurity annotation in our @Configuration:

@Controller public class UserController { @PreAuthorize("hasAuthority('ROLE_USER')") @RequestMapping(value = "/user") public String user(Model model, Principal principal) { ... } }

An overview of all possible authorization options can be found in the official documentation.

As a final modification step, we have to tell the application where our truststore is located and that SSL client authentication is necessary (server.ssl.client-auth=need).

So we put the following into our application.properties:

server.ssl.trust-store=store/truststore.jks server.ssl.trust-store-password=${PASSWORD} server.ssl.client-auth=need

Now, if we run the application and point our browser to //localhost:8443/user, we become informed that the peer cannot be verified and it denies to open our website.

5.3. Client-side Certificate

Now it's time to create the client-side certificate. The steps we need to take, are pretty much the same as for the server-side certificate we already created.

First, we have to create a certificate signing request:

openssl req -new -newkey rsa:4096 -nodes -keyout clientBob.key –out clientBob.csr

We'll have to provide information that will be incorporated into the certificate. For this exercise, let's only enter the common name (CN) – Bob. It's important as we use this entry during the authorization and only Bob is recognized by our sample application.

Next, we need to sign the request with our CA:

openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in clientBob.csr -out clientBob.crt -days 365 -CAcreateserial

The last step we need to take is to package the signed certificate and the private key into the PKCS file:

openssl pkcs12 -export -out clientBob.p12 -name "clientBob" -inkey clientBob.key -in clientBob.crt

Finally, we're ready to install the client certificate in the browser.

Again, we'll use Firefox:

  1. Type about:preferences in the address bar
  2. Open Advanced -> View Certificates -> Your Certificates
  3. Click on Import
  4. Locate the Baeldung tutorials folder and its subfolder spring-security-x509/store
  5. Select the clientBob.p12 file and click OK
  6. Input the password for your certificate and click OK

Now, when we refresh our website, we'll be prompted to select the client certificate we'd like to use:

If we see a welcome message like “Hello Bob!”, that means everything works as expected!

6. Mutual Authentication With XML

Adding X.509 client authentication to an http security configuration in XML is also possible:

 ...         ... 

To configure an underlying Tomcat, we have to put our keystore and our truststore into its conf folder and edit the server.xml:

Tip: With clientAuth set to “want”, SSL is still enabled, even if the client doesn't provide a valid certificate. But in this case, we have to use a second authentication mechanism, for example, a login-form, to access the secured resources.

7. Conclusion

In summary, we've learned how to create a self-signed CA certificate and how to use it to sign other certificates.

Additionally, we've created both, server-side and client-side certificates. Then we've presented how to import them into a keystore and a truststore accordingly.

Furthermore, you now should be able to package a certificate together with its private key into the PKCS12 format.

We've also discussed when it makes sense to use Spring Security X.509 client authentication, so it is up to you, to decide, whether to implement it into your web application, or not.

And to wrap up, find the source code to this article on Github.