23
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

От самописных грабель к дзену: как я почти похоронил свой проект, но нашел Spring Security

Время чтения 14 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
23
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

«А знаешь, парень, если ты умеешь хранить JWT в кармане, а не в кукисах, то никакие сессии тебе уже не страшны. Ну, почти».
Будда, если бы он шарил в девопсе и устал перерождаться

Важное предупреждение. Всё, что вы прочтете ниже, — галлюциногенный трип по закоулкам джавовой экосистемы. Не принимайте за документацию. Автор отдыхал, пока его сервисы плясали под дудку Spring Cloud Gateway. Если вы ждете священных скрижалей, идите на сайт Spring. Там скучно — всё веселье тут.

От самописных грабель к дзену: как я почти похоронил свой проект, но нашел Spring Security

Глава первая. Где я огреб проблем, или Почему @EnableWebSecurity вызывает привыкание

Помните мои прошлые страдания про Великое Отчуждение, в которых я вытащил авторизацию из монолита, и про консьержа (Gateway), который делает вид, что ничего не решает, но на самом деле тащит?

Короче, я переехал из бетонной коробки в мультимодульный отель Stateless Dream. Там поселились:

  • Auth-отдел (знает про вас всё);
  • Gateway (консьерж с усами, разводит клиентов по номерам);
  • Бэкенд (рабочие с кирками и лопатами, копают нейронки для Ollama).

Всё вроде круто, но есть нюанс: я, как последний дурак, написал всю эту авторизацию с нуля — генерацию JWT, RSA-ключи, хеширование паролей через BCrypt, который я сам подключил. 

Чувствовал себя Творцом, пока в одну прекрасную секунду меня не осенило: я изобрел колесо. И теперь пытаюсь продать его магазину Ferrari, где оригинальные запчасти уже лежат на полке.

— Слушай сюда,  сказал бы Будда, если бы был прогером. — Тот, кто пишет свой фильтр аутентификации, обречен перерождаться бесконечно. В каждом новом проекте. В теле усталого, замученного джуниора. Сансара циклов — вот твой удел, сынок.

Знакомьтесь: Spring Security. Это не «либа», это кармический долг перед всем миром Java. Вы его не выбираете, но он выбирает вас.

Глава вторая. А что у нас в холодильнике, или Почему я использовал только 60% Spring Security

В моем ollama-client сейчас задействована едва ли половина возможностей Spring Security — по сути, только асимметричный JWT. 

Что есть сейчас (реализовано моими руками):

  • TokenValidationService — колхозный парсер JWT по публичному ключу.
  • WebSocket Interceptor — проверяет Bearer перед тем, как дать добро на рукопожатие.
  • Пользователи в MongoDB. Пароли хешируются вручную вызовом BCryptPasswordEncoder. (Тут я молодец, хоть стандартный энкодер взял, а не свой rot13 написал).

Диагноз: «Парень, твой JWT просто пушка. Но ты забыл, что у тебя есть еще мозги и руки. Ты используешь Spring Security как дубину неандертальца, хотя это швейцарский нож с подсветкой и лазером».

Глава третья. Фичи, о которых я не знал, или О чём я плачу по ночам в подушку

Вещи, которые лежали прямо перед носом, но я их не видел, потому что был уверен, что сам всё сделаю лучше.

OAuth2 Client: войти через Гугл, не вынимая рук из карманов

Сейчас, чтобы зайти в мой чат, нужно ввести логин и пароль. Скука смертная. 90-е на дворе, что ли? Никто не хочет запоминать эти 2a$10$....

Я мог бы добавить всего одну зависимость: spring-boot-starter-oauth2-client. И написать в конфиге:


java
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.oauth2Login(Customizer.withDefaults()).build();
}

 

И всё! Через 10 минут ты заходишь под ником из Телеграма (понятно, не сегодня или не у нас), с Гитхаба или почты Гугла. Никаких тебе RegisterRequest, никакой возни с имейлом.

Конспиролог внутри меня: «Подключая OAuth2, ты продаешь душу Корпорации. Но ты и так в Матрице. Так какая разница?»

Method Security: волшебный пендель на уровне методов

Сейчас у меня в бэкенде это выглядит так (и это болит):


java
if (!tokenValidationService.isTokenValid(token)) {
return ResponseEntity.status(401).build();
}

 

Я копирую эту строчку в каждый контроллер. На ста эндпойнтах в одном забуду, и всё — дыра.

Перфекционист кричит: «Хватит гонять эти проверки в контроллерах! Обезопась сам метод!»

Аннотация @PreAuthorize:


java
@PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
public List<ChatSession> getSessions(String username) { ... }

 

Вызовешь от имени Васи, даже из веб-сокета, — получишь подзатыльник. Магия.

Remember-Me: куки, которые переживут ваш брак

У меня токен живет сутки, потом нужно логиниться заново. Как в отеле, где в полдень вышвыривают, даже если ты спишь. А ведь можно сделать «запомни меня на 30 дней». Spring Security кладет в браузер долгоживущую куку. Приходит пользователь, Gateway видит куку и автоматически перевыпускает Access Token, даже если старый умер.

— Ваша честь,  скажу я в суде. — Я не взламывал. Это Spring сам подставил плечо. Я просто вернулся после двухнедельного отпуска.

CSRF: защита от дураков

В своем API я выключил CSRF. Для JWT это норма. Но если бы я строил старый добрый сайт с формами… Будда (в гневе): «Выключишь CSRF на формах — родишься капчей для ботов». Spring сам генерит уникальный токен для каждой формы. Без него запрос типа <img src="http://bank/transfer?to=hacker"> сделает вас нищим.

LDAP: для тех, кто работает в аду на углях

Представьте: мой чат ставят в Сбере (чем черт не шутит?). У них нет MongoDB с паролями. У них есть сервер с LDAP, поднятый еще при Брежневе.

Spring Security умеет подхватывать такое:


java
auth.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.contextSource().url("ldap://...");

 

Я подключаюсь к чужой галактике, не меняя свой код. Гениально.

Контроль сессий: сколько девчонок можно позвать в одну комнату?

У меня пользователь alexey откроет 100 вкладок Chrome и получит 100 валидных токенов. А что, если я хочу, как в Телеграме: одно устройство — одна сессия? Зашел второй раз — первую выкинуло. В Spring Security можно так:


properties
spring.session.max-sessions=1

 

В моем stateless-мире с JWT это сложнее: нужно подключать Redis — зато связка Spring Session + Security решает проблему в два счета.

Апгрейд энкодинга на лету: тихая замена 

Я храню BCrypt. Допустим, через два года его взломают. Что делать? Миграцию БД? Да нет, можно использовать DelegatingPasswordEncoder.

В базе лежит {bcrypt}$2a$10.... Пользователь логинится, а Spring Security видит: «О, старье… Сейчас я его на лету пересохраню в новом формате». Без даунтайма и боли.

Философ улыбнулся бы: «Реальность подстраивается под наблюдателя. Хороший фреймворк может переписать прошлое».

Глава четвертая. Как я убил свой велосипед одной строчкой

В проекте я сделал правильную асимметрию: приватный ключ в Auth, публичный — везде.

Но Spring Security умеет делать всё еще проще:


yaml
spring.security.oauth2.resourceserver.jwt.jwk-set-uri: http://auth:8082/.well-known/jwks.json

 

Auth сам отдает ключи по стандартному URL. Бэкенд просто говорит: «Я ресурс-сервер. Мне неинтересно, откуда ключи. Доверяю».

Мне надо выкинуть в мусорку свой TokenValidationService на 20 строк кода. И заменить на одну строчку в application.yml.

Глава пятая. Итоги. А был ли мальчик?

Spring Security — это не фреймворк, а футуристичный протез. Для ньюфага — это жесть и боль. Конфиги не работают, дока врет, ты плачешь. Для матерого волка (как я) — это спасение.

Мой проект на GitVerse — дом из мусора и палок, но он стои́т. А если бы я с самого начала обложился Spring Security как подушками, то:

  • были бы OAuth2-входы «в два клика»;
  • методы были бы защищены @PreAuthorize;
  • не парился бы про утечку BCrypt;
  • не писал бы этот чертов велосипед для валидации!

— Не плоди сущностей, сынок,  скажет Будда. — У тебя есть Spring Security. Просто добавь зависимость и прими его карму. Твоя задача — не победить систему, а договориться с ней. И да, JWT — это круто. Но даже Дзен-мастер иногда хранит сессии в Redis. Потому что клиент бывает… ну, сам знаешь каким.

Что получилось в итоге (список на салфетке):

  • MVP уже есть. Безопасно, токены летают, сервисы не знают друг о друге.
  • Скрытые плюшки. OAuth2, ACL, MFA — это ключи от новых уровней. Лежат в коробке.
  • Путь развития. Последовательный. Не надо ломать старое, только добавлять новое сверху.

P. S. Ветка auth моего проекта на GitVerse открыта. Смотрите, как я наступал на грабли. Если хотите увидеть настоящий Ад, перепишите всё на @PreAuthorize и OAuth2. Нервные клетки — за мой счет. 

P. P. S. В следующей серии посмотрим еще на одну-две технологии. Не переключайтесь. Будет больно.

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники