Привет, это снова Егор Гаврилов. Сегодня я расскажу, что было сделано за последний месяц в рамках очередного своего пет-проекта - StingrayTV Alice.
Предыдущая статья была вынесена в черновики мной, однако если вкратце, StingrayTV Alice - это попытка интегрировать ресиверы Триколора на базе платформы StingrayTV с сервисом "Дом с Алисой". Это позволяет управлять ресивером через Алису, и интегрировать его в общий умный дом. Проект пережил несколько доработок, и сейчас там используется Keycloak, Spring Boot 4, и другие самые современные технологии. Также было сделано множество улучшений кодовой базы, что позволило избавиться от лишнего кода, и улучшить стабильность и производительность данного гейтвея.
Keycloak: теперь нормальная аутентификация - это реальность
Изначально планировалась аутентификация по физическому присутствию пользователя за консолью сервера. Однако реализовать это достаточно было нетривиально, и поэтому принято решение использовать уже готовый сервер аутентификации - а именно Keycloak. Оно даёт более гибкий контроль за процессом аутентификации, а также является проверенным и готовым решением для реализации OAuth2.
Мы его реализовали на базе Spring OAuth2 Resource Server. Для этого достаточно одного конфигурационного класса, и одного параметра в application.yml
/**
* Security configuration for Keycloak authentication.
*/
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/v1.0").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(
jwt -> jwt.jwtAuthenticationConverter(new KeycloakConverter())
))
.exceptionHandling(exception -> exception
.authenticationEntryPoint((request, response, authException) -> {
log.warn("Authentication failed for request: {} {}",
request.getMethod(), request.getRequestURI());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Unauthorized\",\"message\":\"Authentication required\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
log.warn("Access denied for request: {} {}",
request.getMethod(), request.getRequestURI());
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Forbidden\",\"message\":\"Access denied\"}");
})
)
.cors(Customizer.withDefaults());
return http.build();
}
}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${KEYCLOAK_ISSUER_URI:http://keycloak:8080/auth/realms/stingray}
Куча рефакторинга
Проект подвергся обширному рефакторингу - как те, которые я сделал на всех своих пет-проектах (в частности, перевод проектов на Spring Boot 4, а также улучшения по части CI/CD в проектах - теперь там реализован полноценный пайплайн, который обеспечивает высокий уровень консистентности всего цикла), так и постепенная работа над чисткой кода (при помощи самых разных линтинг-инструментов - начиная от встроенных инструментов OpenIDE, и заканчивая SonarQube for IDE и Explyt Spring). Это позволило обеспечить гораздо большую чистоту и сопровождаемость кода.
В частности:
Избавились от кривого механизма аутентификации - теперь там самый что ни на есть цивильный Keycloak.
Убрали использование Preferences API для хранения нужных ключей для старого механизма аутентификации - Keycloak куда лучше во всём.
Мелкие улучшения в кодовой базе - меньше ужаса и треша, больше чистого кода.
Итоги
Проект пережил многое - но теперь оно всё ближе к тому, чтобы можно было использовать у себя дома, безопасно и спокойно.