Ответ
Архитектура OAuth2/OpenID Connect в Java
1. Spring Security OAuth2 Client (рекомендуемый подход)
@Configuration
public class OAuth2LoginConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login**", "/error**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(authenticationSuccessHandler)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
}
2. Кастомизация User Service
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2User oauth2User = delegate.loadUser(userRequest);
// Извлечение атрибутов из провайдера
Map<String, Object> attributes = oauth2User.getAttributes();
String registrationId = userRequest.getClientRegistration().getRegistrationId();
if ("google".equals(registrationId)) {
String email = (String) attributes.get("email");
String name = (String) attributes.get("name");
// Сохранение/обновление пользователя в БД
}
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
attributes,
"email" // nameAttributeKey
);
}
}
3. Конфигурация провайдеров
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_SECRET}
scope: openid, profile, email
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_SECRET}
scope: user:email, read:user
provider:
keycloak:
issuer-uri: ${KEYCLOAK_ISSUER_URI}
user-name-attribute: preferred_username
4. JWT-валидация для OpenID Connect
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("https://idp.example.com/.well-known/jwks.json")
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder()))
);
return http.build();
}
5. Основные провайдеры и их особенности
| Провайдер | Протокол | Основные scope | Особенности |
|---|---|---|---|
| OpenID Connect | openid, profile, email |
Поддержка G Suite, проверка домена | |
| GitHub | OAuth2 | user, repo |
Только OAuth2 (не OpenID Connect) |
| Microsoft | OpenID Connect | openid, profile, User.Read |
Интеграция с Azure AD |
| Keycloak | OpenID Connect | Настраиваемые | Self-hosted, ролевая модель |
6. Безопасность и best practices
- PKCE (Proof Key for Code Exchange) для public clients
- State parameter для защиты от CSRF
- Хранение токенов: Access token в памяти, Refresh token в secure cookie
- Валидация issuer и audience в JWT
- Автоматическое обновление токенов через
OAuth2AuthorizedClientManager
Ответ 18+ 🔞
Слушай, я тут подумал, ну что за пиздец творится с этой авторизацией в вебе? Все эти OAuth, OpenID, JWT... Просто ёперный театр какой-то! Но если разобраться, то, блядь, не так страшен чёрт, как его малюют. Вот смотри, как это в Java, на Spring'е, делается, чтобы не обосраться.
1. Вот этот самый Spring Security OAuth2 Client (делай так, не еби мозг)
Ну, типа, главный фильтр, который всё решает. Создаёшь конфиг и пишешь правила. Главное — не накосячить с матчерами путей, а то получится, что все в твой /admin зайдут, как к себе домой.
@Configuration
public class OAuth2LoginConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login**", "/error**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(authenticationSuccessHandler)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
}
Вот видишь? Разрешил всем ходить на главную, логин и ошибки. А на всё остальное — только авторизованным. И подключил вход через OAuth2. Вроде ничего сложного, да? Хуй с ним, разберёшься.
2. А это — сервис, который разбирается, кто пришёл
Вот это уже поинтереснее. Прилетает пользователь от Гугла или Гитхаба, а ты должен понять, кто это такой, и либо создать ему запись в своей базе, либо обновить старую. Типа, встречаем по одёжке — атрибутам.
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2User oauth2User = delegate.loadUser(userRequest);
// Извлечение атрибутов из провайдера
Map<String, Object> attributes = oauth2User.getAttributes();
String registrationId = userRequest.getClientRegistration().getRegistrationId();
if ("google".equals(registrationId)) {
String email = (String) attributes.get("email");
String name = (String) attributes.get("name");
// Сохранение/обновление пользователя в БД
}
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")),
attributes,
"email" // nameAttributeKey
);
}
}
Смотри, логика простая: смотрим, от кого пришёл запрос (registrationId). Если от Гугла — лезем в атрибуты, вытаскиваем почту и имя. И тут же, блядь, в свою базу его пихаем или обновляем. А на выходе создаём стандартного юзера Spring'а с ролью USER. Всё, пиздец, готово.
3. Конфигурация в application.yml — тут всё красиво
Тут просто копируй и подставляй свои ключи. Главное — не закоммить их в публичный репозиторий, а то будет овердохуища веселья, когда на твоём аккаунте начнут майнить биткоины.
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_SECRET}
scope: openid, profile, email
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_SECRET}
scope: user:email, read:user
provider:
keycloak:
issuer-uri: ${KEYCLOAK_ISSUER_URI}
user-name-attribute: preferred_username
Видишь? Для Гугла просим стандартные scope: openid, profile, email. Для Гитхаба — свои. А Keycloak — это вообще отдельная песня, self-hosted штука, но тоже настраивается.
4. А если прилетает просто JWT-токен? (OpenID Connect)
Бывает, что фронт сам авторизуется и присылает тебе уже готовый токен. Твоя задача — проверить, не поддельный ли он. Для этого нужен JwtDecoder, который сходит по известному адресу и возьмёт публичные ключи для проверки подписи.
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("https://idp.example.com/.well-known/jwks.json")
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder()))
);
return http.build();
}
Вот и вся магия. Настроил Resource Server, указал, какой декодер использовать, и Spring сам будет проверять каждый входящий токен. Удобно, блядь!
5. Таблица провайдеров, чтобы не путаться
Смотри, какие бывают варианты, и чем они отличаются. А то начнёшь к Гитхабу с openid лезть — он тебе, сука, ничего не даст.
| Провайдер | Протокол | Основные scope | Особенности |
|---|---|---|---|
| OpenID Connect | openid, profile, email |
Поддержка G Suite, проверка домена | |
| GitHub | OAuth2 | user, repo |
Только OAuth2 (не OpenID Connect) |
| Microsoft | OpenID Connect | openid, profile, User.Read |
Интеграция с Azure AD |
| Keycloak | OpenID Connect | Настраиваемые | Self-hosted, ролевая модель |
Запомни главное: Гугл и Мелкомягкие — используют OpenID Connect (то есть, помимо доступа, они ещё и личность подтверждают). А Гитхаб — старый добрый OAuth2, только для делегирования доступа. Не перепутай, а то будешь как дурак выглядеть.
6. И напоследок — как не обосраться с безопасностью
Тут, блядь, внимание, это важно:
- PKCE — это must have для мобилок и одностраничников. Без него — как без штанов.
- State parameter — всегда включай. Это чтобы тебя не наебали CSRF-атакой.
- Токены — Access token храни в оперативке, а Refresh token — в защищённой,
HttpOnlyкуке. Не выёбывайся с LocalStorage. - Валидация JWT — всегда проверяй
issuer(кто выпустил) иaudience(для кого). А то прилетит токен от левого провайдера, а ты его примешь. - Обновление токенов — Spring умеет это делать автоматически через
OAuth2AuthorizedClientManager. Используй, не изобретай велосипед.
Вот и всё, ебать мои старые костыли. Вроде ничего сложного, да? Главное — начать, а там уже по ходу дела разберёшься, где и какую хуйню поправить. Удачи, и не накосячь!