Комментарии 11
С огромным уважением отношусь к любой статье, прекрасно представляю сколько работы и усилий потребовалось, чтобы ее написать. Но как автор статьи вы принимаете определенную ответственность, так как даете советы, как делать, и мне кажется, эти ваши советы в этой статье — вредные.
Этот комментарий для читателей, которые наверняка захотят применить подход из этой статьи или миллиона ей подобных, которые легко гуглятся по запросу Spring Security + login + jwt и прочим.
Дорогие читатели, это — не Spring Security. Это творческий, очень-очень-очень кастомизированный, не всегда безопасный и не всегда корректный способ авторизации. Если вы его скопируете в проект, то почти наверняка либо получите проблемы с безопасностью, либо головную боль с поддержкой. Документация по Spring Security доступна на официальном сайте, так же есть набор гайдов от команды Spring. Это поддерживаемые и одобренные (а часто написанные) командой Spring материалы, которые сделают ваши приложения безопасными и легко поддерживаемыми.
Теперь более детально.
- Basic auth, который используется в статье, по определению stateless, ему не нужны ни сессии, ни JWT. Но часто авторы таких статей пытаются сделать Form Login + JWT. И чаще всего, это бессмысленно и бесполезно — сессия гораздо безопаснее и для form login подходит лучше (stateless безопасность за скобками).
- JWT токены есть смысл использовать не для first party авторизации (когда ваше же приложение и хранит пароли), а для 3rd party — когда надо аутентифицировать пользователя не зная его пароль. Для этого есть целая спецификация OAuth2 + OpenID.
- CSRF это не просто такой удобный способ хранить JWT токены, это фундаментально разные вещи.
- В ваших примерах Spring Security вообще, по сути, не нужен. Вся безопасность написана с нуля, используя кастомные фильтры.
NoOpPasswordEncoder.getInstance
— никогда, абсолютно никогда его не надо использовать! Даже для теста. Даже для примера.
Таких пунктов можно еще штук 10 можно написать. Это — не Spring Security, и это не Spring, так делать не надо!
Респект за использование spring-boot-devtools
, очень полезная в разработке вещь!
На Resource Server нет приватного ключа, разумеется, но он не нужен. Любой клиент Authorization Server-a может спросить у него публичные ключи чтобы валидировать токен. Но в остальном да, идея такая, это стандарт JWS.
Auth-z Server (который еще ранняя альфа) и Resource Server работают по стандартам OAuth2, так что, именно так.
Еще такой вопрос: а какую аутентификацию (не обязательно OAuth2) оптимально использовать для REST API, если оно и для браузерного SPA-приложений, и, в перспективе, для мобильного? При этом чтобы масштабировать горизонтально было возможно (то есть stateless). Из коробки JWT в Spring Security не поддерживается (почему?). Есть Simple Hash-Based Token Approach, но звучит так, что он просто нужен для сохранения состояния между сессиями. Это так, или именно его вместо JWT надо использовать?
OAuth2 это протокол авторизации, но построенный поверх него OpenID это сейчас фактически стандарт, так что я бы использовал его. Только именно стандарт, а не самописную реализацию удобного подмножества протокола.
Из коробки JWT прекрасно поддерживается в Spring Security.
Я уже насколько много прочитал статей и вопросов "Spring Security + JWT + stateless", что рискну предположить — "не поддерживается" это означает "нельзя при Form Login вместо сессии использовать JWT из cookie"? Это как раз самописная реализация подмножества протокола, под нее загнуть Spring Security можно, но я бы трижды подумал. Там раскидано много граблей, надо точно знать куда ступать. Если же уверенности в хождении по граблям нет, то я бы советовал строго следовать протоколу OpenID, тогда Spring Security будет работать прекрасно.
Классическая архитектура для stateless безопасности, чтобы не писать ее самому это:
Authorization Server (либо внешний Auth0 / Okta / etc, либо on premise Hydra или типа того, можно даже Spring Authorization Server посмотреть). Это и будет единой точкой аутентификации — и мобильное приложение, и сайт для доступа по REST API будут ходить сюда. Только тут живет информация о пользователях — логины, MFA, пароли, профили.
Resource Server — это тот самый REST API, Spring Security легко сконфигурить, нужно только указать
issuer-uri
.
SPA которое использует OAuth2 Implicit Grant, получает токены из Auth-z сервера и отправляет в Authorization header в Resource Server.
Мобильное приложение использует PKCE + Authorization Code через тот же Auth-z server.
В таком подходе 2-4 будут полностью stateless и не знать ничего о пользователях, кроме токена. Вся работа с аутентификацией будет в Authorization Server (1), и чаще всего если его и надо будет масштабировать, то там будут свои решения у каждого сервера. Во многих случаях его можно будет даже оставить stateful, при правильной настройке Token expiration + refresh token, логинится туда заново пользователям придется не часто.
рискну предположить — «не поддерживается» это означает «нельзя при Form Login вместо сессии использовать JWT из cookie»?
Да. Не из cookie, а из Authorization header, но не суть…
Просто все знают, что для SPA нужен stateless токен, начинаешь искать подходящий. А оказывается, надо Authorization Server развернуть. ПС точнее, не stateless токен, а stateless API, jwt-токен то как раз может хранить состояние.
Для SPA не нужен stateless токен, он там бесполезен, если у вас SPA поверх одного приложения так и планируется в дальнейшем.
Токен нужен для организации аутентификации/авторизации в микросервисной среде, где точка входя одна а потребитлей авторизации много и хорошо бы им единую сущность кидать
REST API с использованием Spring Security и JWT