Spring Security SSL. Авторизация с помощью сертификата.

Добрый день.

Сегодня мы рассмотрим такую возможность Spring security, как аутентификация пользователей с помощью TLS сертификата. Так называемая mutual authentication.

Для начала сгенерируем сертификаты для клиента и сервера. Примеров того, как это сделать в интернете достаточно, приведём один из них.

Генерируем сертификат сервера

Генерируем RSA ключ сервера

openssl genrsa -aes256 -out serverprivate.key 2048

Генерируем CA сертификат с использоваие данного ключа. Большинство полей можно заполнить на ваше усмотрение, кроме поля CN, который должен быть равен домену, который мы хотим защитить. В нашем случае - это localhost.

openssl req -x509 -new -nodes -key serverprivate.key -sha256 -days 1024 -out serverCA.crt

Импортируем сертификат в Java truststore. Задаём дефолтный пароль changeit

keytool -import -file serverCA.crt -alias serverCA -keystore truststore.jks

Экспортируем сертификат в keystore, пароль снова changeit

openssl pkcs12 -export -in serverCA.crt -inkey serverprivate.key -certfile serverCA.crt -out keystore.p12

Генерируем клиентский сертификат.

Создаём приватный ключ и запрос на получение сертификата. В поле CN указываем имя пользвателя. В нашем случае CN='Alex.Pashnin'.

openssl genrsa -aes256 -out clientprivate.key 2048
openssl req -new -key clientprivate.key -out client.csr

Создаём сертификат.

openssl x509 -req -in client.csr -CA serverCA.crt -CAkey serverprivate.key -CAcreateserial -out client.crt -days 365 -sha256

 

Перейдём к созданию тестового приложения

Воспользуемся для этого gradle.

gradle.build

//Плангин для быстрого старта Spring Boot приложений. Работает только с gradle версии 4.4+.
//В случае проблем с версией закомментировать, выполнить gradle wrapper --gradle-version=4.10.2
//и раскомментировать обратно
plugins {
    id 'org.springframework.boot' version '2.1.0.RELEASE'
}

apply plugin: 'java'
apply plugin: 'io.spring.dependency-management'

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    compile 'org.slf4j:slf4j-api:1.7.21'
    compile 'org.springframework.boot:spring-boot-starter-security:2.1.0.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
}

 

Далее создадим главный класс приложения

@EnableWebSecurity
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CertAuthServer extends WebSecurityConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(CertAuthServer.class, args);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and().csrf().disable()
                .x509()
                .subjectPrincipalRegex("CN=(.*?),")
                .userDetailsService(userDetailsService());
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) {
                if (username.equals("Alex.Pashnin")) {
                    return new User(username, "",
                            AuthorityUtils
                                    .commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
                return null;
            }
        };
    }
}

Создадим простой веб котроллер, который будет приветствовать авторизованного пользователя.

@RestController
@RequestMapping(value = "/test")
public class TestController {
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @GetMapping("/hello")
    public String hello(Principal principal) {
        UserDetails currentUser
                = (UserDetails) ((Authentication) principal).getPrincipal();
        return "Hello " + currentUser.getUsername()+"!";
    }
}

Конфигурационный файл application.properties

server.ssl.key-store = classpath:keystore.p12
server.ssl.trust-store = classpath:truststore.jks
server.ssl.trust-store-password = changeit
server.ssl.key-store-password = changeit
server.ssl.key-password = changeit
server.ssl.protocol = TLS
server.ssl.key-alias = 1
server.ssl.client-auth = need
server.ssl.enabled = true
server.port = 8443

Поскольку я не задал алиас для серверного ключа, посмотрим его в хранилище. Необходимое нам поле - Alias name, его и задаём в server.ssl.key-alias.

$ keytool -v -list -keystore keystore.p12 
Enter keystore password:  
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: 1
Creation date: Nov 12, 2018
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=localhost, OU=dev, O=tune-it, ST=Some-State, C=RU
Issuer: CN=localhost, OU=dev, O=tune-it, ST=Some-State, C=RU

ВАЖНО!

Для корректной работы данного приложения, отсутсвия неожиданных логов логов из разряда

 o.s.s.w.a.p.x.X509AuthenticationFilter   : No client certificate found in request. 

следует отключить csrf и добавить server.ssl.client-auth = need в application.properties

 

Запускаем наше приложение

$ gradle wrapper
$ ./gradlew bootRun

Убеждаемся в его работоспособности

$ curl -k --cert client.crt --key clientprivate.key -X GET 'https://localhost:8443/test/hello'

Hello Alex.Pashnin!

 

Таким, довольно нехитрым образом, мы произвели авторизацию и аутентификацию пользователя с помощью TLS сертификата. На этом всё, спасибо за внимание.