Добрый день.
Сегодня мы рассмотрим такую возможность 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 сертификата. На этом всё, спасибо за внимание.