OAuth2 (без тестов и ошибка с java)
This commit is contained in:
@@ -47,6 +47,8 @@ dependencies {
|
|||||||
implementation(libs.spring.grpc.starter)
|
implementation(libs.spring.grpc.starter)
|
||||||
implementation(libs.grpc.services)
|
implementation(libs.grpc.services)
|
||||||
|
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.4.3")
|
||||||
|
|
||||||
runtimeOnly(libs.micrometer.registry.prometheus)
|
runtimeOnly(libs.micrometer.registry.prometheus)
|
||||||
runtimeOnly(libs.h2)
|
runtimeOnly(libs.h2)
|
||||||
runtimeOnly(libs.postgresql)
|
runtimeOnly(libs.postgresql)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ grpc-java = "1.60.0"
|
|||||||
springdoc = "2.8.6"
|
springdoc = "2.8.6"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" }
|
||||||
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
|
spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" }
|
||||||
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
|
spring-boot-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator" }
|
||||||
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }
|
spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security" }
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class UserRepository(
|
|||||||
id = UUID.fromString(rs.getString("id")),
|
id = UUID.fromString(rs.getString("id")),
|
||||||
name = rs.getString("name"),
|
name = rs.getString("name"),
|
||||||
email = rs.getString("email"),
|
email = rs.getString("email"),
|
||||||
|
password = rs.getString("password"),
|
||||||
library = null,
|
library = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -56,6 +57,15 @@ class UserRepository(
|
|||||||
return users.firstOrNull()
|
return users.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findByEmail(email: String): User? {
|
||||||
|
val users = jdbc.query(
|
||||||
|
"SELECT id, name, email, password FROM users WHERE email = ?",
|
||||||
|
userRowMapper,
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
return users.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
override fun findAll(): List<User> =
|
override fun findAll(): List<User> =
|
||||||
jdbc.query(
|
jdbc.query(
|
||||||
"SELECT id, name, email FROM users",
|
"SELECT id, name, email FROM users",
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
import com.project.movienight.application.ports.output.IdGenerator
|
||||||
|
import com.project.movienight.application.ports.output.UserRepositoryPort
|
||||||
|
import com.project.movienight.domain.model.User
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class CustomOAuth2UserService(
|
||||||
|
private val userRepository: UserRepositoryPort,
|
||||||
|
private val idGenerator: IdGenerator,
|
||||||
|
) : DefaultOAuth2UserService() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(CustomOAuth2UserService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadUser(userRequest: OAuth2UserRequest): OAuth2User {
|
||||||
|
val oAuth2User = super.loadUser(userRequest)
|
||||||
|
val registrationId = userRequest.clientRegistration.registrationId
|
||||||
|
|
||||||
|
log.debug("Processing OAuth2 login for provider: {}", registrationId)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User)
|
||||||
|
val user = findOrCreateUser(userInfo)
|
||||||
|
UserPrincipal.create(user, oAuth2User.attributes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("OAuth2 authentication failed: ${e.message}", e)
|
||||||
|
throw OAuth2AuthenticationException("Failed to process OAuth2 user data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findOrCreateUser(userInfo: OAuth2UserInfo): User {
|
||||||
|
val existingUser = userRepository.findByEmail(userInfo.getEmail())
|
||||||
|
|
||||||
|
return if (existingUser != null) {
|
||||||
|
log.debug("User found by email: {}", userInfo.getEmail())
|
||||||
|
existingUser
|
||||||
|
} else {
|
||||||
|
log.debug("Creating new user for provider: {}", userInfo.getProvider())
|
||||||
|
val newUser = User(
|
||||||
|
id = idGenerator.generateId(),
|
||||||
|
name = userInfo.getName(),
|
||||||
|
email = userInfo.getEmail(),
|
||||||
|
password = "", // OAuth2 пользователи не имеют пароля
|
||||||
|
library = null,
|
||||||
|
)
|
||||||
|
userRepository.save(newUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
class GoogleOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
|
||||||
|
override fun getProviderId(): String = attributes["sub"] as String
|
||||||
|
|
||||||
|
override fun getEmail(): String = attributes["email"] as String
|
||||||
|
|
||||||
|
override fun getName(): String = attributes["name"] as String
|
||||||
|
|
||||||
|
override fun getProvider(): String = "google"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
interface OAuth2UserInfo {
|
||||||
|
fun getProviderId(): String
|
||||||
|
fun getEmail(): String
|
||||||
|
fun getName(): String
|
||||||
|
fun getProvider(): String
|
||||||
|
fun getAttributes(): Map<String, Any>
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
|
||||||
|
object OAuth2UserInfoFactory {
|
||||||
|
|
||||||
|
fun getOAuth2UserInfo(registrationId: String, user: OAuth2User): OAuth2UserInfo {
|
||||||
|
val attributes = user.attributes
|
||||||
|
|
||||||
|
return when (registrationId.lowercase()) {
|
||||||
|
"google" -> GoogleOAuth2UserInfo(attributes)
|
||||||
|
"yandex" -> YandexOAuth2UserInfo(attributes)
|
||||||
|
"vk" -> VkOAuth2UserInfo(attributes)
|
||||||
|
else -> throw OAuth2AuthenticationException("Unknown provider: $registrationId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
import com.project.movienight.domain.model.User
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class UserPrincipal(
|
||||||
|
private val user: User,
|
||||||
|
private val attributes: Map<String, Any>? = null,
|
||||||
|
) : OAuth2User, UserDetails {
|
||||||
|
|
||||||
|
fun getId(): UUID = user.id
|
||||||
|
|
||||||
|
override fun getName(): String = user.name
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap()
|
||||||
|
|
||||||
|
override fun getAuthorities(): Collection<GrantedAuthority> {
|
||||||
|
return listOf(SimpleGrantedAuthority("ROLE_USER"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPassword(): String = user.password
|
||||||
|
|
||||||
|
override fun getUsername(): String = user.email
|
||||||
|
|
||||||
|
override fun isAccountNonExpired(): Boolean = true
|
||||||
|
|
||||||
|
override fun isAccountNonLocked(): Boolean = true
|
||||||
|
|
||||||
|
override fun isCredentialsNonExpired(): Boolean = true
|
||||||
|
|
||||||
|
override fun isEnabled(): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(user: User, attributes: Map<String, Any>? = null): UserPrincipal {
|
||||||
|
return UserPrincipal(user, attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class VkOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
|
||||||
|
override fun getProviderId(): String {
|
||||||
|
val response = attributes["response"] as? List<Map<String, Any>>
|
||||||
|
return response?.firstOrNull()?.get("id")?.toString() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEmail(): String = attributes["email"] as? String ?: ""
|
||||||
|
|
||||||
|
override fun getName(): String {
|
||||||
|
val response = attributes["response"] as? List<Map<String, Any>>
|
||||||
|
val first = response?.firstOrNull()
|
||||||
|
val firstName = first?.get("first_name") as? String ?: ""
|
||||||
|
val lastName = first?.get("last_name") as? String ?: ""
|
||||||
|
return "$firstName $lastName".trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProvider(): String = "vk"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.project.movienight.adapters.security.oauth2
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class YandexOAuth2UserInfo(
|
||||||
|
private val attributes: Map<String, Any>
|
||||||
|
) : OAuth2UserInfo {
|
||||||
|
|
||||||
|
override fun getProviderId(): String = attributes["id"]?.toString() ?: ""
|
||||||
|
|
||||||
|
override fun getEmail(): String {
|
||||||
|
val emails = attributes["emails"] as? List<Map<String, String>>
|
||||||
|
return emails?.firstOrNull()?.get("value") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName(): String = attributes["display_name"] as? String ?: ""
|
||||||
|
|
||||||
|
override fun getProvider(): String = "yandex"
|
||||||
|
|
||||||
|
override fun getAttributes(): Map<String, Any> = attributes
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ interface UserRepositoryPort {
|
|||||||
|
|
||||||
fun findById(id: UUID): User?
|
fun findById(id: UUID): User?
|
||||||
|
|
||||||
|
fun findByEmail(email: String): User?
|
||||||
|
|
||||||
fun findAll(): List<User>
|
fun findAll(): List<User>
|
||||||
|
|
||||||
fun deleteById(id: UUID)
|
fun deleteById(id: UUID)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class UserService(
|
|||||||
id = idGenerator.generateId(),
|
id = idGenerator.generateId(),
|
||||||
name = command.name,
|
name = command.name,
|
||||||
email = command.email,
|
email = command.email,
|
||||||
|
password = "",
|
||||||
library = null,
|
library = null,
|
||||||
)
|
)
|
||||||
return userRepository.save(user)
|
return userRepository.save(user)
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ data class User(
|
|||||||
val id: UUID,
|
val id: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val email: String,
|
val email: String,
|
||||||
|
val password: String,
|
||||||
val library: FilmLibrary?,
|
val library: FilmLibrary?,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user