Comments 20
Объясню почему я не хотел использовать Spring Security. Он мне показался слишком сложным и его как-то не очень удобно использовать в REST.
Дальше не читал
Время жизни токена 24 часа? А как в случае его компрометации его отзывать? Для этого существует подход с access/refresh токенами.
RBAC? Проверка уровней доступа на нескольких уровнях? Сделайте его кастомно и получится Spring Security.
Передача токена в заголовках? Исходя из вашей статьи вы разрабатываете сайты, то есть токен хранится на фронте.
Если да, то токены должны храниться только в куках с HTTP/Secure only. Иначе компрометируется это удовольствие на ура.
Время жизни токена 24 часа? А как в случае его компрометации его отзывать?
Удалить из базы. А вообще, смотря как будет устроена работа с JWT.
Для этого существует подход с access/refresh токенами
Это можно настроить как вам хочется. А вообще всегда делаю, что бы при входе на сайт, вызывался метод API, который проверяет токен, и в случае если проверка пройдена успешно, то делается refresh
Проверка уровней доступа на нескольких уровнях?
На каких нескольких?
Передача токена в заголовках?
А почему нет? Можно и в куках, можно и через хедер.
Исходя из вашей статьи вы разрабатываете сайты, то есть токен хранится на фронте.
Если да, то токены должны храниться только в куках с HTTP/Secure only.
Все именно так и происходит.
А если по сути, то статья не со всем по JWT и работу с access/refresh токенами. Статья про то, как пробросить авторизованного юзера в метод контроллера.
Удалить из базы.
Смысл JWT токена как раз в том, что он нигде не хранится. Поэтому отозвать его нельзя. Отзывается, обычно, как раз refresh токен.
На каких нескольких?
RBAC на уровне контроллера/сервиса/репозитория. С союзом И. Служит для того, чтобы в случае ошибки программиста не дать пользователю с правами READ право записать что-то в базу.
можно и через хедер.
Конечно, можно. Но только его надо где-то будет хранить на стороне браузера. Обычно это local/session storage. А к нему имеет доступ клиентский javascript. Привет компрометации токена.
как пробросить авторизованного юзера в метод контроллера
SecurityContextHolder.getContext().getAuthentication()
Есть такие штуки, как лучшие практики. И перед тем как предлагать production-ready решение, а уж тем более писать статьи об этих решениях на профильные ресурсы их лучше изучить.
Удалить из базы, серьезно? Но ведь основная фича JWT — stateless! Все, что нужно для проверки токена — сам токен
Жесть. Возьмите хотя бы Keycloak by RedHat. Это сертифицированное решение. Будет вам и OAuth, и JWT и интеграция со Spring Security из коробки, просто при помощи стартера Spring Boot.
Да и в методах обработки endpoint'а все равно, наверное, придется доставать юзера из контекста. Возможно я не прав, так как не сильно в нем разбирался, но статья в любом случае не об этом. (...) Мне нужно было что-то простое и удобное в использовании. Пришла идея сделать это через аннотацию.
Эта замечательная идея не только вам пришла в голову.
Spring Security предоставляет @PreAuthorize, @PreFilter, @PostAuthorize и @PostFilter, внутри которых можно писать SpEL, и вызывать стандартные и кастомные методы проверки доступа.
"Доставать юзера из контекста" очень просто. Объект Authentication со всей информацией о юзере и его грантах доступен вас из любого места приложения, через публичный статический метод контекста. И всё это полностью поддерживается в юнит и интеграционных тестах.
«Доставать юзера из контекста» очень просто
Этого я и хотел избежать. Я хотел что бы юзер уже просто был, и его не надо было ниоткуда доставать.
Объект Authentication со всей информацией о юзере и его грантах доступен вас из любого места приложения, через публичный статический метод контекста
Использование статических методов, это очень профессионально
PS Лучше почитать про спринг секюрити. Для вашей задачи нужно было написать 15 строк кода и вы бы получили крутой и безопасный функционал у вас в программме а не велосипед с врзможно дырой в безопасности.
PPS Ваша реализация получения пользователя для контроля доступа неправильна даже аритектурно — так как контроль доступа и бизнес логика должны быт разделены, что и предоставляет спринг секюрити.
контроль доступа и бизнес логика должны быт разделены
А где они смешаны?
ЗЫ Если нужно было контролировать, что пользователь зарегистрирован, то опять это можно было сделать в виде АОР и какой-то аннотации, например @AuthenticationNeeded, что более читаемо.
ЗЫЗЫ В спринг секюрити это выглядело-бы так @PreAuthorize(«isAuthenticated()»). F А если нужно роль проверить @PreAuthorize(«hasRole('ROLE_ADMIN')»). А если нужно значение параметра проверить @PreAuthorize("#param=123"). А если все сложно, вызвать бин @PreAuthorize("@bean.check(#param)")
Ну вы же не просто так целый объект User в виде параметра передаете
Вы имеете ввиду, в метод контроллера? Если да, то понятно почему. Мне же надо знать, в контроллере, кто хочет совершить эти действия, что бы они отразились именно на нем. Например он хочет изменить свои данные. В таком случае мне и нужен весь User. Да и в других случаях тоже нужен юзер, или как минимум его id.
Скорее всего дальше вы из него что-то вытягиваете, например список групп, и смотрите, есть ли определенная группа или нет
Опять же, где дальше? В самом контроллере? Если нужна логика ролей, то это все можно опять же передать в аннотацию, и там все это проверять
ЗЫЗЫ В спринг секюрити это выглядело-бы так
А как в security получить юзера, кроме как дергать его из контекста? От этого я и хотел избавиться
Плюс если вы используете спрингтдата, то в sql полностью доступен spel через который можно получить пользователя. Другими словами, вам не нужен напрямую пользователь в контролере, а информация о нем будет автоматически используя spel добавлена в sql запрос. Например
Query(«select * from users where id=?#{principal.id}»)
Использование статических методов, это очень профессионально
Наверное, лекциями Егора Бугаенко увлекаетесь?
Так вот, static — это нормально в двух случаях:
1. Когда у нас утилитная функция и она идемпотентна. Такие функции не надо мокать. Тот же Apache Commons на 90 процентов состоит из таких функций. И их использование — это хорошая практика.
2. При обращении к ThreadLocal. Как бы некоторые эксперты не критиковали подобный механизм многие задачи без него просто не решаются. Например, попробуйте решить задачу сквозного прокидывания в логи уникального requestId запроса. Не переписывая при этом половины проекта. SecurityContextHolder внутри как раз и держат ThreadLocal. Так что ничего зазорного в том, чтобы обратиться к нему нет.
А вот в остальных ситуациях статические методы это действительно зло. Классический пример такого зла это System.currentTimeMillis(), который, к слову, не мокается вообще ничем.
Объект Authentication со всей информацией о юзере и его грантах доступен вас из любого места приложения, через публичный статический метод контекста
как раз и есть смешивание бизнес логики и контроля доступа. В каком-нибудь сервисе, мне должно быть все равно, кто пришел и хочет это сделать. Тот у кого нет прав это делать, просто не должен был дойти до этого метода
Spring. Кастомная аутентификация с применением JWT