дописала security

This commit is contained in:
Elena Ponomareva
2026-05-07 07:58:11 +03:00
parent 27317c00e4
commit 6158d3543c
8 changed files with 145 additions and 48 deletions
+2 -2
View File
@@ -49,8 +49,8 @@ dependencies {
implementation(libs.spring.grpc.starter) implementation(libs.spring.grpc.starter)
implementation(libs.grpc.services) implementation(libs.grpc.services)
// Temporarily disabled due to OAuth2 configuration issues
// implementation(libs.spring.boot.starter.oauth2.client) implementation(libs.spring.boot.starter.oauth2.client)
runtimeOnly(libs.micrometer.registry.prometheus) runtimeOnly(libs.micrometer.registry.prometheus)
runtimeOnly(libs.h2) runtimeOnly(libs.h2)
@@ -5,7 +5,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAu
import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
@SpringBootApplication(exclude = [OAuth2ClientAutoConfiguration::class]) @SpringBootApplication
@ConfigurationPropertiesScan("com.project.movienight.config") @ConfigurationPropertiesScan("com.project.movienight.config")
class MovieNightApplication class MovieNightApplication
@@ -9,6 +9,7 @@ import com.project.movienight.domain.model.User
import org.springframework.jdbc.core.JdbcTemplate import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.sql.ResultSet import java.sql.ResultSet
import java.time.LocalDateTime
import java.util.UUID import java.util.UUID
@Repository @Repository
@@ -27,18 +28,32 @@ class UserRepository(
} }
override fun save(user: User): User { override fun save(user: User): User {
val entity = user.toEntity() val existingUser = findById(user.id)
val updatedRows =
jdbc.update( val entity = if (existingUser != null) {
""" val existingEntity = existingUser.toEntity()
UPDATE users user.toEntity(
SET name = ?, email = ? provider = existingEntity.provider?.let { AuthProvider.valueOf(it) },
WHERE id = ? providerId = existingEntity.providerId,
""".trimIndent(), createdAt = existingEntity.createdAt
entity.name,
entity.email,
entity.id,
) )
} else {
user.toEntity()
}
val updatedRows = jdbc.update(
"""
UPDATE users
SET name = ?, email = ?, provider = ?, provider_id = ?
WHERE id = ?
""".trimIndent(),
entity.name,
entity.email,
entity.provider,
entity.providerId,
entity.id,
)
if (updatedRows == 0) { if (updatedRows == 0) {
jdbc.update( jdbc.update(
""" """
@@ -57,31 +72,28 @@ class UserRepository(
} }
override fun findById(id: UUID): User? { override fun findById(id: UUID): User? {
val entities = val entities = jdbc.query(
jdbc.query( "SELECT id, name, email, provider, provider_id, created_at FROM users WHERE id = ?",
"SELECT id, name, email, provider, provider_id, created_at FROM users WHERE id = ?", userEntityRowMapper,
userEntityRowMapper, id,
id, )
)
return entities.firstOrNull()?.toDomain() return entities.firstOrNull()?.toDomain()
} }
override fun findByEmail(email: String): User? { override fun findByEmail(email: String): User? {
val entities = val entities = jdbc.query(
jdbc.query( "SELECT id, name, email, provider, provider_id, created_at FROM users WHERE email = ?",
"SELECT id, name, email, provider, provider_id, created_at FROM users WHERE email = ?", userEntityRowMapper,
userEntityRowMapper, email,
email, )
)
return entities.firstOrNull()?.toDomain() return entities.firstOrNull()?.toDomain()
} }
override fun findAll(): List<User> = override fun findAll(): List<User> =
jdbc jdbc.query(
.query( "SELECT id, name, email, provider, provider_id, created_at FROM users",
"SELECT id, name, email, provider, provider_id, created_at FROM users", userEntityRowMapper,
userEntityRowMapper, ).map { it.toDomain() }
).map { it.toDomain() }
override fun deleteById(id: UUID) { override fun deleteById(id: UUID) {
jdbc.update("DELETE FROM users WHERE id = ?", id) jdbc.update("DELETE FROM users WHERE id = ?", id)
@@ -91,16 +103,16 @@ class UserRepository(
provider: AuthProvider, provider: AuthProvider,
providerId: String, providerId: String,
): User? { ): User? {
val entities = val entities = jdbc.query(
jdbc.query( """
""" SELECT id, name, email, provider, provider_id, created_at
SELECT id, name, email, provider, provider_id, created_at FROM users FROM users
WHERE provider = ? AND provider_id = ? WHERE provider = ? AND provider_id = ?
""".trimIndent(), """.trimIndent(),
userEntityRowMapper, userEntityRowMapper,
provider.name, provider.name,
providerId, providerId,
) )
return entities.firstOrNull()?.toDomain() return entities.firstOrNull()?.toDomain()
} }
} }
@@ -0,0 +1,42 @@
package com.project.movienight.adapters.security
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfiguration(
private val customOAuth2UserService: CustomOAuth2UserService,
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.oauth2Login { oauth2 ->
oauth2
.userInfoEndpoint { userInfo ->
userInfo.userService(customOAuth2UserService)
}
.defaultSuccessUrl("/api/users/me", true)
}
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/", "/login/**", "/oauth2/**", "/h2-console/**", "/actuator/health").permitAll()
.requestMatchers("/api/users/me").authenticated()
.requestMatchers("/api/**").authenticated()
.anyRequest().authenticated()
}
.headers { headers ->
headers.frameOptions { frameOptions ->
frameOptions.sameOrigin()
}
}
.csrf { csrf ->
csrf.disable()
}
return http.build()
}
}
@@ -10,15 +10,16 @@ import java.util.UUID
class UserPrincipal( class UserPrincipal(
private val user: User, private val user: User,
private val attributes: Map<String, Any>? = null, private val attributes: Map<String, Any>? = null,
) : OAuth2User, ) : OAuth2User, UserDetails {
UserDetails {
fun getId(): UUID = user.id fun getId(): UUID = user.id
override fun getName(): String = user.name override fun getName(): String = user.name
override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap() override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap()
override fun getAuthorities(): Collection<GrantedAuthority> = listOf(SimpleGrantedAuthority("ROLE_USER")) override fun getAuthorities(): Collection<GrantedAuthority> =
listOf(SimpleGrantedAuthority("ROLE_USER"))
override fun getPassword(): String = "" override fun getPassword(): String = ""
@@ -33,9 +34,7 @@ class UserPrincipal(
override fun isEnabled(): Boolean = true override fun isEnabled(): Boolean = true
companion object { companion object {
fun create( fun create(user: User, attributes: Map<String, Any>? = null): UserPrincipal =
user: User, UserPrincipal(user, attributes)
attributes: Map<String, Any>? = null,
): UserPrincipal = UserPrincipal(user, attributes)
} }
} }
@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
//import com.project.movienight.adapters.security.UserPrincipal
import java.util.UUID import java.util.UUID
@RestController @RestController
@@ -73,4 +74,12 @@ class UserController(
fun delete( fun delete(
@PathVariable id: UUID, @PathVariable id: UUID,
) = deleteUserUseCase.delete(id) ) = deleteUserUseCase.delete(id)
/*
@GetMapping("/me")
fun getCurrentUser(principal: UserPrincipal): UserResponse =
UserResponse.fromDomain(
getUserByIdUseCase.getById(principal.getId())
)
*/
} }
+32
View File
@@ -25,6 +25,38 @@ spring:
console: console:
enabled: ${SPRING_H2_CONSOLE_ENABLED:true} enabled: ${SPRING_H2_CONSOLE_ENABLED:true}
path: /h2-console path: /h2-console
security:
oauth2:
client:
registration:
google:
client-id: ${OAUTH2_GOOGLE_CLIENT_ID:test-client-id}
client-secret: ${OAUTH2_GOOGLE_CLIENT_SECRET:test-secret}
scope: email,profile
yandex:
client-id: ${OAUTH2_YANDEX_CLIENT_ID:test-client-id}
client-secret: ${OAUTH2_YANDEX_CLIENT_SECRET:test-secret}
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: login:email,login:avatar
vk:
client-id: ${OAUTH2_VK_CLIENT_ID:test-client-id}
client-secret: ${OAUTH2_VK_CLIENT_SECRET:test-secret}
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: email
provider:
yandex:
authorization-uri: https://oauth.yandex.ru/authorize
token-uri: https://oauth.yandex.ru/token
user-info-uri: https://login.yandex.ru/info
user-name-attribute: id
vk:
authorization-uri: https://oauth.vk.com/authorize
token-uri: https://oauth.vk.com/access_token
user-info-uri: https://api.vk.com/method/users.get?v=5.131&fields=photo_200
user-name-attribute: response
server: server:
shutdown: graceful shutdown: graceful
@@ -0,0 +1,3 @@
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_provider_provider_id
ON users(provider, provider_id)
WHERE provider IS NOT NULL AND provider_id IS NOT NULL;