diff --git a/build.gradle.kts b/build.gradle.kts index 8be4085..5c89058 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,8 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") + implementation("org.springframework.boot:spring-boot-starter-data-jdbc") + runtimeOnly("org.postgresql:postgresql") implementation("org.jetbrains.kotlin:kotlin-reflect") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") diff --git a/src/main/kotlin/ru/vyatsu/qr_access_auth_server/KotlinRegisteredClientRowMapper.kt b/src/main/kotlin/ru/vyatsu/qr_access_auth_server/KotlinRegisteredClientRowMapper.kt new file mode 100644 index 0000000..a486cb3 --- /dev/null +++ b/src/main/kotlin/ru/vyatsu/qr_access_auth_server/KotlinRegisteredClientRowMapper.kt @@ -0,0 +1,66 @@ +package ru.vyatsu.qr_access_auth_server + +import org.springframework.security.oauth2.core.AuthorizationGrantType +import org.springframework.security.oauth2.core.ClientAuthenticationMethod +import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientRowMapper +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient +import org.springframework.util.StringUtils +import java.sql.ResultSet + +class KotlinRegisteredClientRowMapper : RegisteredClientRowMapper() { + + override fun mapRow(rs: ResultSet, rowNum: Int): RegisteredClient? { + val clientIdIssuedAt = rs.getTimestamp("client_id_issued_at") + val clientSecretExpiresAt = rs.getTimestamp("client_secret_expires_at") + val clientAuthenticationMethods = + StringUtils.commaDelimitedListToSet(rs.getString("client_authentication_methods")) + val authorizationGrantTypes = StringUtils.commaDelimitedListToSet(rs.getString("authorization_grant_types")) + val redirectUris = StringUtils.commaDelimitedListToSet(rs.getString("redirect_uris")) + val postLogoutRedirectUris = StringUtils.commaDelimitedListToSet(rs.getString("post_logout_redirect_uris")) + val clientScopes = StringUtils.commaDelimitedListToSet(rs.getString("scopes")) + val builder = RegisteredClient.withId(rs.getString("id")) + .clientId(rs.getString("client_id")) + .clientIdIssuedAt(clientIdIssuedAt?.toInstant()) + .clientSecret(rs.getString("client_secret")) + .clientSecretExpiresAt(clientSecretExpiresAt?.toInstant()) + .clientName(rs.getString("client_name")) + .clientAuthenticationMethods { authenticationMethods -> + clientAuthenticationMethods.forEach { authenticationMethod -> + authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod)) + } + } + .authorizationGrantTypes { grantTypes -> + authorizationGrantTypes.forEach { grantType -> + grantTypes.add(resolveAuthorizationGrantType(grantType)) + } + } + .redirectUris { uris -> uris.addAll(redirectUris) } + .postLogoutRedirectUris { uris -> + uris.addAll(postLogoutRedirectUris) + } + .scopes { scopes -> scopes.addAll(clientScopes) } + return builder.build() + } + + private fun resolveAuthorizationGrantType(authorizationGrantType: String): AuthorizationGrantType { + return if (AuthorizationGrantType.AUTHORIZATION_CODE.value == authorizationGrantType) { + AuthorizationGrantType.AUTHORIZATION_CODE + } else if (AuthorizationGrantType.CLIENT_CREDENTIALS.value == authorizationGrantType) { + AuthorizationGrantType.CLIENT_CREDENTIALS + } else { + if (AuthorizationGrantType.REFRESH_TOKEN.value == authorizationGrantType) AuthorizationGrantType.REFRESH_TOKEN + else AuthorizationGrantType(authorizationGrantType) + } + } + + private fun resolveClientAuthenticationMethod(clientAuthenticationMethod: String): ClientAuthenticationMethod { + return if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.value == clientAuthenticationMethod) { + ClientAuthenticationMethod.CLIENT_SECRET_BASIC + } else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.value == clientAuthenticationMethod) { + ClientAuthenticationMethod.CLIENT_SECRET_POST + } else { + if (ClientAuthenticationMethod.NONE.value == clientAuthenticationMethod) ClientAuthenticationMethod.NONE + else ClientAuthenticationMethod(clientAuthenticationMethod) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/vyatsu/qr_access_auth_server/SecurityConfig.kt b/src/main/kotlin/ru/vyatsu/qr_access_auth_server/SecurityConfig.kt index 234eb67..dec916f 100644 --- a/src/main/kotlin/ru/vyatsu/qr_access_auth_server/SecurityConfig.kt +++ b/src/main/kotlin/ru/vyatsu/qr_access_auth_server/SecurityConfig.kt @@ -1,5 +1,6 @@ package ru.vyatsu.qr_access_auth_server +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.nimbusds.jose.jwk.JWKSet import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jose.jwk.source.ImmutableJWKSet @@ -8,18 +9,17 @@ import com.nimbusds.jose.proc.SecurityContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.annotation.Order +import org.springframework.jdbc.core.JdbcTemplate import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.oauth2.core.AuthorizationGrantType -import org.springframework.security.oauth2.core.ClientAuthenticationMethod +import org.springframework.security.jackson2.SecurityJackson2Modules import org.springframework.security.oauth2.jwt.JwtDecoder -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient +import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer +import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.SecurityFilterChain @@ -51,31 +51,25 @@ class SecurityConfig { fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain { http.csrf { it.disable() } .authorizeHttpRequests { it.anyRequest().authenticated() } - return http.build() } @Bean fun userDetailsService(): UserDetailsService { - return InMemoryUserDetailsManager( - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - ) + return InMemoryUserDetailsManager() } @Bean - fun registeredClientRepository(): RegisteredClientRepository { - val testClient = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId("test-client") - .clientSecret("{noop}secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build() - - return InMemoryRegisteredClientRepository(testClient) + fun registeredClientRepository(operations: JdbcTemplate): RegisteredClientRepository { + val clientRepository = JdbcRegisteredClientRepository(operations) + val clientRowMapper = KotlinRegisteredClientRowMapper() + val classLoader = JdbcRegisteredClientRepository::class.java.classLoader + val objectMapper = jacksonObjectMapper() + objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader)) + objectMapper.registerModule(OAuth2AuthorizationServerJackson2Module()) + clientRowMapper.setObjectMapper(objectMapper) + clientRepository.setRegisteredClientRowMapper(clientRowMapper) + return clientRepository } @Bean diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e69de29..ed7e5e5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -0,0 +1,7 @@ +spring: + application: + name: qr-access-auth-server + datasource: + url: jdbc:postgresql://localhost:5432/qr_access + username: qr_access_user + password: 123 \ No newline at end of file