Pull to refresh

Comments 9

Не очень подходящий кейс: "… мы разрабатываем защищенное веб приложение, имеющее доступ к ресурсам пользователя на внешнем сервере, ..., а в качестве внешнего сервера выступает Bitbucket, и мы не хотели бы хранить у себя логин и пароль пользователя от внешней системы."
Получается Вы нарушаете безопасность своего защищенного приложения подключением к неконтролируемому с Вашей стороны сервису Bitbucket ради того, чтобы только он знал о логин-паролях к Вашему приложению. Бред какой-то…

По сути это обычная авторизация по протоколу OAuth2, где Bitbucket является сервером авторизации. Логин происходит с помощью учетной записи Bitbucket, других учетных данных нет, так что все вполне безопасно.

Поделка, годная для первой половины 10-ых. Ну, или гайд для новичков для вхождения в Java-SS-OAuth2. На практике малоприменимая вещь.

Я сам достаточно глубоко в этом, довел данную связку почти до «из коробки», поэтому могу сказать, что Spring Security последних версий может намного больше. Авторизация через внешние ресурсы — это очень спорная штука:
а) доступность — гарантия, что завтра это будет работать? сервер вообще, линки, структура;
б) безопасность — да, ресурс, которому верят, но ...;
в) задержки — для безопасности expire_on ставится небольшой, значит, надо относительно часто бегать за токеном, с удаленным ресурсом с нагрузкой возможны задержки;
г) доступность — как в предыдущем, только есть гарантия, что ресурс не имеет ограничений на запросы (с IP, в минуту и т.д.)? — риск.

Spring Security на борту уже имеет AuthorizationServer и ResourceServer, а также OAuth2Client. Сейчас достаточно модно (и удобно) создавать мультисервисную архитектуру проекта. Так почему не сделать один модуль — авторизационный OAuth2 сервер? Нет всех ограничений, описанных выше, плюс полный контроль. Как бонус — разделение БД на доступную для сервисов-модулей с данными проекта и отдельно авторизационную с монопольным доступом из авторизационного модуля. Еще бонус — не примитивный токет, а OAuth2 token + JWT + OpenID, это значительно увеличивает удобство пользования такой авторизацией модулями.

Кроме этого, грамотная реализация OAuth2 авторизации включает в себя не только access token, но и refresh token, для оптимизации запросов к авторизационному модулю/серверу.
Также на практике более гибкая логика модуля, где применяются scopes, а не authorities — это позволяет очень гибко обеспечивать секьюрность методов и классов. То есть у customers назначены authorities, покрывающие определенные наборы scopes, а методы классов в большинстве своем защищены определением разрешенных scopes, типа
@PreAuthorize("#oauth2.hasScope('mod:some_scope')")

Кстати, с такой реализацией не только пользователи имеют авторизацию, но и подключаемые модули, у всех свои clientId и clientSecret. Плюс использование OAuth2RestTemplate, что делает еще и сами запросы к серверу секьюрными.

Спасибо за работу над статьей, особенно полезны для новичков будут детали протокола OAuth2. Но и здесь есть важный момент — сам по себе OAuth2 это не протокол аутентификации, это протокол авторизации. А вот OpenID Connect это да, аутентификация, и Spring Security его отлично поддерживает.


Но все, что касается Spring Security в статье это, скорее bad practices, так делать не надо.


  1. Для начала, стоит указать версию SS — поддержка OAuth2 в 5-й версии внесена в ядро и сильно отличается от 4-й.
  2. Если используется Spring Boot, то руками создавать ClientRegistration не нужно, SB это сделает за вас. Более того, Boot из коробки поможет сконфигурировать стандартные OpenID провайдеры вроде Facebook, Google, Github, даже Token URI не надо указывать.
  3. Ну и самое главное — своя реализация UserService не просто не нужна, а даже вредна — например, тут не проверяются многие пункты стандарта, не надо так делать в реальных проектах. Вся конфигурация, в принципе, делается так: .oauth2Login(). Все, SS создаст стандартный UserService для работы с OpenID endpoint (/userinfo), если провайдер поддерживает, провалидирует ответ, создаст IdToken и вообще сделает все правильно и хорошо.
Спасибо, что ознакомились со статьей и представили свои замечания. Согласен, что OAuth2 — протокол авторизации, но ведь в статье так и написано. Статья и правда задумывалась как введение в OAuth2 для новичков и не претендует на пример идеального кода (возможно стоило указать это в статье сразу).
  1. В реализации используется 5-я версия и она действительно отличается от 4-й, хотя в целом конфигурирование приведенных вещей очень похоже и там и там.
  2. Согласен, в данном случае создавать его руками было не обязательно. Хотя Bitbucket не является стандартным провайдером, поэтому все тоже самое пришлось бы сделать в application.properties. Вариант с application.properties не подойдет, если нужно динамически создавать ClientRegistration в зависимости от каких-либо условий. В данном случае пример был вырезан из более крупного приложения и переносить это в application.properties руки не дошли.
  3. Насчет своей реализации UserService можно и согласиться и нет. Поддерживаю, что у этого решения есть свои минусы, но это решение использует стандартное API SS, поэтому тоже имеет право на существование.
— Своя реализация UserService не просто не нужна, а даже вредна.

А как быть, если ВКонтакте на запрос юзера (по user-info-uri) не Map возвращает, а сложную структуру c массивом из одного элемента — который и есть авторизовавшийся юзер:

    response: {
         [
            { 
                 id: "123",
                 first_name: "имя"
                 ...
             }
         ]
   }
}

DefaultOAuth2UserService, как я понимаю, только простейший объект без вложенности парсит.
Пробую создать VkOAuth2User implements OAuth2User и добавить в конфигурацию, как ниже. Это правильное направление, или куда копать?
http.oauth2Login(oauth2Login ->
                oauth2Login
                        .userInfoEndpoint(userInfoEndpoint ->
                                userInfoEndpoint
                                        .customUserType(VkOAuth2User.class, "vk")

                        )

Я, кажется, имел в виду что вредна конкретно в этом случае. Своя реализация UserDetailsService нужна как раз чтобы поддержать такие вот сложные и нестандартные ответы от UserInfo Endpoint. Ответ от ВК выглядит странно, такое ощущение что он не следует спецификации. Тогда да, придется писать свой сервис.


.customUSerType уже deprecated, и рекомендуется использовать свой сервис и делегацию.

И еще вопрос: можно ли задать свой OAuth2AccessTokenResponseClient только для одного провайдера (для vk, токен тоже нестандартно выдает). Если делать как тут, то ломаются остальные провайдеры (github и прочие)
http.oauth2Login().tokenEndpoint().accessTokenResponseClient(accessTokenResponseClient());

По-сути, вопрос стоит в параллельной поддержке OAuth2 стандарта и стандарта VK, не думаю, что Spring Security умеет это из коробки. Скорее всего тоже придется писать свою делегирующую обертку, которая для ВК будет вызывать одно, для других провайдеров другое.

Sign up to leave a comment.

Articles