Skip to content

[auth] ThirdPartyScope 기능 구현#230

Open
wwwcomcomcomcom wants to merge 21 commits intodevelopfrom
feat/oauth
Open

[auth] ThirdPartyScope 기능 구현#230
wwwcomcomcomcom wants to merge 21 commits intodevelopfrom
feat/oauth

Conversation

@wwwcomcomcomcom
Copy link
Collaborator

개요

#106 에서 언급되었던 ThirdPartyScope 기능을 구현합니다.

본문

  1. JWT 서명을 RSA 방식으로 변경하여서 외부인도 검증할 수 있도록 변경하였습니다.
  • JWK set 방식으로 public key를 조회할 수 있도록 API를 추가했습니다.
  1. Application 도메인, Application 과 ThirdPartyScope 엔티티를 추가하였습니다.
  • Application은 ThirdPartyScope의 사용처를 제공하는 외부 서비스를 나타내는 도메인입니다.
  1. JWT claim에 ThirdPartyScope를 포함하도록 변경하였습니다.
  • ThirdPartyScope의 경우 category:name이 아닌 applicationId:name으로 나타냅니다.

@github-actions github-actions bot added the waiting for review:검토 대기 확인을 대기하고 있습니다 label Mar 11, 2026
@github-actions github-actions bot requested review from ZaMan-O and hongjm0912 and removed request for Copilot March 11, 2026 07:45
@wwwcomcomcomcom wwwcomcomcomcom added the enhancement:개선작업 새 기능 또는 기존 코드 개선에 관한 내용입니다 label Mar 11, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 OAuth 시스템에 ThirdPartyScope 기능을 도입하고, JWT 서명 방식을 RSA로 전환하여 외부 서비스와의 연동을 강화합니다. Application 도메인을 통해 외부 서비스가 자체적인 권한 범위를 정의하고 관리할 수 있게 하며, JWT에 이 ThirdPartyScope 정보를 포함시켜 유연한 권한 관리를 지원합니다. 또한, 외부에서 JWT 유효성을 검증할 수 있도록 JWK Set API를 제공합니다.

Highlights

  • RSA JWT 서명 및 JWK Set API 구현: JWT 서명 방식이 HMAC에서 RSA로 변경되어 외부 서비스도 JWT를 검증할 수 있게 되었으며, 이를 위해 JWK Set을 제공하는 API가 추가되었습니다.
  • ThirdPartyScope 기능 도입: ThirdPartyScope 기능이 구현되어 외부 서비스(Application)가 자체적인 권한 범위를 정의하고 관리할 수 있게 되었으며, JWT 클레임에 이 정보가 포함됩니다.
  • Application 및 ThirdPartyScope 관리 API 추가: Application 및 ThirdPartyScope를 생성, 조회, 수정, 삭제, 검색할 수 있는 REST API가 datagsm-web 모듈에 추가되었습니다.
  • OAuthScope 확장성 개선: 기존 OAuthScope enum 클래스가 open class로 변경되어 ThirdPartyScope가 이를 상속받아 확장될 수 있도록 구조가 개선되었습니다.
Changelog
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/request/AddThirdPartyScopeReqDto.kt
    • Added DTO for adding a third-party scope.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/request/CreateApplicationReqDto.kt
    • Added DTO for creating an application, including a nested scope DTO.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/request/ModifyApplicationReqDto.kt
    • Added DTO for modifying an application's name.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/request/ModifyThirdPartyScopeReqDto.kt
    • Added DTO for modifying a third-party scope.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/request/SearchApplicationReqDto.kt
    • Added DTO for searching applications with pagination parameters.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/response/ApplicationListResDto.kt
    • Added DTO for application list responses, including total pages and elements.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/dto/response/ApplicationResDto.kt
    • Added DTO for application responses, including nested scope DTOs.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/entity/ApplicationJpaEntity.kt
    • Added JPA entity for applications, including relationships to accounts and third-party scopes.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/entity/ThirdPartyScopeJpaEntity.kt
    • Added JPA entity for third-party scopes, linked to applications.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/repository/ApplicationJpaRepository.kt
    • Added JPA repository for ApplicationJpaEntity, extending custom search functionality.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/repository/ThirdPartyScopeJpaRepository.kt
    • Added JPA repository for ThirdPartyScopeJpaEntity, including a method to find by application ID and scope name.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/repository/custom/ApplicationJpaCustomRepository.kt
    • Added custom repository interface for searching applications with paging.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/application/repository/custom/impl/ApplicationJpaCustomRepositoryImpl.kt
    • Added implementation for custom application search using QueryDSL.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/client/entity/constant/OAuthScope.kt
    • Changed OAuthScope from an enum class to an open class to allow extension.
    • Introduced a list of builtin OAuthScope values.
    • Updated fromString and getAllScopes methods to use builtinValues.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/client/entity/constant/ThirdPartyScope.kt
    • Added ThirdPartyScope class, extending OAuthScope, to represent application-specific scopes.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/oauth/dto/response/JwkSetResDto.kt
    • Added DTO for JWK Set responses.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/oauth/dto/response/RsaPublicJwkDto.kt
    • Added DTO for RSA public JSON Web Key (JWK), including a utility function for creation.
  • datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/data/OauthJwtEnvironment.kt
    • Modified to include private key, public key, and key ID for RSA JWT configuration.
  • datagsm-oauth-authorization/src/main/kotlin/team/themoment/datagsm/oauth/authorization/domain/oauth/controller/OauthController.kt
    • Imported JwkSetResDto and QueryJwkSetService.
    • Injected QueryJwkSetService into the controller.
    • Added a GET /jwks endpoint to expose the JWK Set.
  • datagsm-oauth-authorization/src/main/kotlin/team/themoment/datagsm/oauth/authorization/domain/oauth/service/QueryJwkSetService.kt
    • Added interface for querying the JWK Set.
  • datagsm-oauth-authorization/src/main/kotlin/team/themoment/datagsm/oauth/authorization/domain/oauth/service/impl/Oauth2TokenServiceImpl.kt
    • Imported ThirdPartyScopeJpaRepository and ThirdPartyScope.
    • Injected ThirdPartyScopeJpaRepository.
    • Updated scope resolution logic to include ThirdPartyScope from the database.
  • datagsm-oauth-authorization/src/main/kotlin/team/themoment/datagsm/oauth/authorization/domain/oauth/service/impl/QueryJwkSetServiceImpl.kt
    • Added implementation for QueryJwkSetService to return RSA public keys as a JWK Set.
  • datagsm-oauth-authorization/src/main/kotlin/team/themoment/datagsm/oauth/authorization/global/security/jwt/JwtProvider.kt
    • Imported ThirdPartyScope and RSA key related classes.
    • Replaced HMAC secret key with RSA private and public keys for JWT signing and verification.
    • Added key ID to JWT headers.
    • Updated scope claims to map OAuthScope objects to their string representations.
    • Modified scope parsing to handle both OAuthScope and ThirdPartyScope.
    • Added methods to retrieve the public key and key ID.
    • Included companion object methods for loading RSA private and public keys from PEM strings.
  • datagsm-oauth-authorization/src/main/resources/application.yml
    • Updated JWT configuration to use JWT_KEY_ID, JWT_PRIVATE_KEY, and JWT_PUBLIC_KEY environment variables.
  • datagsm-oauth-authorization/src/test/kotlin/team/themoment/datagsm/oauth/authorization/domain/oauth/service/impl/Oauth2TokenServiceImplTest.kt
    • Imported ThirdPartyScopeJpaRepository.
    • Injected mock ThirdPartyScopeJpaRepository into Oauth2TokenServiceImpl.
  • datagsm-oauth-userinfo/src/main/kotlin/team/themoment/datagsm/oauth/userinfo/global/security/jwt/JwtProvider.kt
    • Imported ThirdPartyScope and RSA key related classes.
    • Replaced HMAC secret key with RSA public key for JWT verification.
    • Updated scope parsing to handle both OAuthScope and ThirdPartyScope.
    • Included companion object method for loading RSA public key from a PEM string.
  • datagsm-oauth-userinfo/src/main/resources/application.yml
    • Updated JWT configuration to use JWT_KEY_ID, JWT_PRIVATE_KEY, and JWT_PUBLIC_KEY environment variables.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/controller/ApplicationController.kt
    • Added REST controller for managing Third-party Applications and their scopes, including CRUD operations and search.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/AddThirdPartyScopeService.kt
    • Added service interface for adding a third-party scope to an application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/CreateApplicationService.kt
    • Added service interface for creating an application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/DeleteApplicationService.kt
    • Added service interface for deleting an application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/DeleteThirdPartyScopeService.kt
    • Added service interface for deleting a third-party scope from an application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/ModifyApplicationService.kt
    • Added service interface for modifying an application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/ModifyThirdPartyScopeService.kt
    • Added service interface for modifying a third-party scope.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/QueryApplicationService.kt
    • Added service interface for querying a single application.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/SearchApplicationService.kt
    • Added service interface for searching applications.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/AddThirdPartyScopeServiceImpl.kt
    • Added implementation for adding a third-party scope to an application, including authorization checks.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/CreateApplicationServiceImpl.kt
    • Added implementation for creating an application, including scope initialization and UUID generation.
    • Added an internal extension function to convert ApplicationJpaEntity to ApplicationResDto.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/DeleteApplicationServiceImpl.kt
    • Added implementation for deleting an application, including authorization checks.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/DeleteThirdPartyScopeServiceImpl.kt
    • Added implementation for deleting a third-party scope, including authorization and existence checks.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/ModifyApplicationServiceImpl.kt
    • Added implementation for modifying an application's name, including authorization checks.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/ModifyThirdPartyScopeServiceImpl.kt
    • Added implementation for modifying a third-party scope, including authorization and existence checks.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/QueryApplicationServiceImpl.kt
    • Added implementation for querying a single application by ID.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/application/service/impl/SearchApplicationServiceImpl.kt
    • Added implementation for searching applications with pagination.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/client/service/impl/CreateClientServiceImpl.kt
    • Imported QueryAvailableOauthScopesService.
    • Injected QueryAvailableOauthScopesService.
    • Modified scope validation to use QueryAvailableOauthScopesService, allowing ThirdPartyScope to be considered.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/client/service/impl/QueryAvailableOauthScopesServiceImpl.kt
    • Injected ApplicationJpaRepository.
    • Modified to include ThirdPartyScope from registered applications in the list of available scopes.
    • Ensured builtin scopes are listed before third-party scopes.
  • datagsm-web/src/main/kotlin/team/themoment/datagsm/web/domain/client/util/ClientUtil.kt
    • Removed as its functionality was integrated into QueryAvailableOauthScopesServiceImpl.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/AddThirdPartyScopeServiceTest.kt
    • Added tests for AddThirdPartyScopeService, covering success cases for owners/admins/roots and failure cases for unauthorized users or non-existent applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/CreateApplicationServiceTest.kt
    • Added tests for CreateApplicationService, covering application creation with and without scopes, UUID generation, and owner assignment.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/DeleteApplicationServiceTest.kt
    • Added tests for DeleteApplicationService, covering success cases for owners/admins/roots and failure cases for unauthorized users or non-existent applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/DeleteThirdPartyScopeServiceTest.kt
    • Added tests for DeleteThirdPartyScopeService, covering success cases for owners/admins/roots and failure cases for unauthorized users, non-existent scopes, or mismatched applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/ModifyApplicationServiceTest.kt
    • Added tests for ModifyApplicationService, covering success cases for owners/admins/roots and failure cases for unauthorized users or non-existent applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/ModifyThirdPartyScopeServiceTest.kt
    • Added tests for ModifyThirdPartyScopeService, covering success cases for owners/admins/roots and failure cases for unauthorized users, non-existent scopes, or mismatched applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/QueryApplicationServiceTest.kt
    • Added tests for QueryApplicationService, covering successful retrieval of applications with and without scopes, and handling of non-existent applications.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/application/service/SearchApplicationServiceTest.kt
    • Added tests for SearchApplicationService, covering searching by name, ID, and pagination, as well as cases with no results.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/client/service/CreateClientServiceTest.kt
    • Modified to use mock QueryAvailableOauthScopesService for scope validation.
    • Updated test cases to reflect the new scope validation logic, including handling of invalid ThirdPartyScopes.
  • datagsm-web/src/test/kotlin/team/themoment/datagsm/web/domain/client/service/QueryAvailableOauthScopesServiceTest.kt
    • Modified to use mock ApplicationJpaRepository.
    • Added test cases to verify the inclusion of ThirdPartyScope from applications and the correct ordering of scope groups.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements ThirdPartyScope functionality, including RSA-based JWT signing and the addition of Application and ThirdPartyScope domains, laying the groundwork for external service integration. However, a security analysis identified two potential Denial of Service (DoS) vulnerabilities related to unbounded data retrieval: one in the application search API due to unrestricted pagination, and another from an unbounded findAll() call when querying available OAuth scopes. These issues could lead to resource exhaustion. Additionally, the review identified general improvements related to performance (e.g., N+1 problems). It is recommended to enforce maximum page sizes and implement more efficient querying for scope lists to mitigate these risks.

Comment on lines +14 to +16
@field:Min(1)
@param:Schema(description = "페이지 크기", example = "100", defaultValue = "100")
val size: Int = 100,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The size parameter for pagination has a @Min(1) constraint but lacks a @Max constraint. An attacker can provide an extremely large value for the size parameter (e.g., size=1000000), which is passed directly to the database query's limit clause. This can lead to excessive memory consumption and CPU usage on both the database and application servers, potentially causing a Denial of Service (DoS).

Suggested change
@field:Min(1)
@param:Schema(description = "페이지 크기", example = "100", defaultValue = "100")
val size: Int = 100,
@field:Max(100)
@param:Schema(description = "페이지 크기", example = "100", defaultValue = "100")
val size: Int = 100,

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 ThirdPartyScope 기능을 구현하고, JWT 서명 방식을 RSA로 변경하는 등 여러 변경 사항을 포함합니다. 새로운 ApplicationThirdPartyScope 도메인에 대한 CRUD API와 관련 로직이 체계적으로 잘 구현되었으며, OAuthScope 리팩토링, JWKS 엔드포인트 추가, 상세한 테스트 코드 작성 등 긍정적인 변경 사항이 많습니다. 하지만, 스코프 해결 중 N+1 쿼리를 수행하여 서비스 성능 저하 또는 충돌을 유발할 수 있는 잠재적인 서비스 거부(DoS) 취약점이 확인되었습니다. 또한, ApplicationJpaCustomRepositoryImpl에서 thirdPartyScopes에 대한 N+1 문제가 발생할 수 있으므로, default_batch_fetch_size에 의존하기보다는 ID 기반 페이지네이션과 IN 절을 사용하는 2단계 쿼리 패턴을 적용하여 이 문제를 해결하는 것을 권장합니다.

Comment on lines +16 to +43
override fun searchApplicationWithPaging(
name: String?,
id: String?,
pageable: Pageable,
): Page<ApplicationJpaEntity> {
val content =
jpaQueryFactory
.selectFrom(applicationJpaEntity)
.leftJoin(applicationJpaEntity.account)
.fetchJoin()
.where(
name?.let { applicationJpaEntity.name.startsWith(it) },
id?.let { applicationJpaEntity.id.eq(it) },
).offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.fetch()

val countQuery =
jpaQueryFactory
.select(applicationJpaEntity.count())
.from(applicationJpaEntity)
.where(
name?.let { applicationJpaEntity.name.startsWith(it) },
id?.let { applicationJpaEntity.id.eq(it) },
)

return PageableExecutionUtils.getPage(content, pageable) { countQuery.fetchOne() ?: 0L }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

현재 구현은 searchApplicationWithPaging 메서드에서 ApplicationJpaEntity를 조회할 때 thirdPartyScopes 컬렉션을 함께 조회하지 않아, 이후 toResDto 변환 과정에서 N+1 쿼리 문제가 발생할 수 있습니다. default_batch_fetch_size 설정으로 문제가 완화될 수는 있지만, 프로젝트의 일반 규칙에서는 이 방식보다 'ID 페이지네이션 -> IN 절 fetchJoin' 2단계 쿼리 패턴 사용을 권장하고 있습니다.

N+1 문제를 근본적으로 해결하고 명시적인 데이터 로딩을 위해, 먼저 페이지에 해당하는 Application ID 목록을 조회한 뒤, 해당 ID들을 사용하여 IN 절과 fetchJoin으로 thirdPartyScopes를 포함한 전체 Application 엔티티를 조회하는 방식으로 수정하는 것을 제안합니다. 이렇게 하면 페이지네이션과 컬렉션 fetch join을 함께 사용할 때 발생할 수 있는 문제를 피하고 성능을 개선할 수 있습니다.

    override fun searchApplicationWithPaging(
        name: String?,
        id: String?,
        pageable: Pageable,
    ): Page<ApplicationJpaEntity> {
        val conditions = listOfNotNull(
            name?.let { applicationJpaEntity.name.startsWith(it) },
            id?.let { applicationJpaEntity.id.eq(it) },
        )

        val ids =
            jpaQueryFactory
                .select(applicationJpaEntity.id)
                .from(applicationJpaEntity)
                .where(*conditions.toTypedArray())
                .offset(pageable.offset)
                .limit(pageable.pageSize.toLong())
                .fetch()

        if (ids.isEmpty()) {
            return Page.empty(pageable)
        }

        val content =
            jpaQueryFactory
                .selectFrom(applicationJpaEntity)
                .leftJoin(applicationJpaEntity.account).fetchJoin()
                .leftJoin(applicationJpaEntity.thirdPartyScopes).fetchJoin()
                .where(applicationJpaEntity.id.`in`(ids))
                .fetch()
                .distinct()
                .sortedBy { ids.indexOf(it.id) }

        val countQuery =
            jpaQueryFactory
                .select(applicationJpaEntity.count())
                .from(applicationJpaEntity)
                .where(*conditions.toTypedArray())

        return PageableExecutionUtils.getPage(content, pageable) { countQuery.fetchOne() ?: 0L }
    }
References
  1. To resolve N+1 problems with pagination, use the 'ID pagination -> IN clause fetchJoin 2-query pattern' instead of relying on default_batch_fetch_size as a long-term solution.

Comment on lines +246 to 249
.map { scopeStr ->
OAuthScope.fromString(scopeStr)
?: resolveThirdPartyScope(scopeStr)
}.toSet()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The calculateGrantedScopes method iterates over requested scopes and calls resolveThirdPartyScope for each one that is not a built-in scope. resolveThirdPartyScope performs a database query. An attacker could create a client with a large number of third-party scopes and then request a token with all of them, triggering many database queries in a single request (N+1 problem), which can lead to database connection pool exhaustion and service degradation. It is recommended to optimize the scope resolution by querying all third-party scopes in a single database call using an IN clause.

@snowykte0426 snowykte0426 mentioned this pull request Mar 11, 2026
Comment on lines +35 to +38
if (application.account != currentAccount && !isAdmin) {
throw ExpectedException("ThirdPartyScope 추가 권한이 없습니다.", HttpStatus.FORBIDDEN)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 Hibernate 1차 캐시 덕분에 동일 트랜젝션 내부에서 단일 인스턴스가 반환되며 문제가 없는것은 맞으나 간단한 수정으로 비교 실패 이슈를 완전히 차단할 수 있을 것 같아 제안드립니다!

Suggested change
if (application.account != currentAccount && !isAdmin) {
throw ExpectedException("ThirdPartyScope 추가 권한이 없습니다.", HttpStatus.FORBIDDEN)
}
if (application.account.id != currentAccount.id && !isAdmin) {
throw ExpectedException("ThirdPartyScope 추가 권한이 없습니다.", HttpStatus.FORBIDDEN)
}

이런식으로 교체하여 명시적인 비교를 하면 좋을 것 같습니다,해당 코드가 사용되는 DeleteApplicationServiceImpl.kt, ModifyApplicationServiceImpl.kt 등에서도요!

Comment on lines +7 to 10
val privateKey: String,
val publicKey: String,
val keyId: String,
val accessTokenExpiration: Long,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 키가 추가되며 userinfo 모듈에도 동일한 설정이 필요해지는데 개선 방안이 없을까요? Environment를 분리하거나 기본값 할당을 하고 코드에 주석달아놓는 식으로요

Comment on lines +15 to +16
@param:Schema(description = "Third-party 스코프 목록")
val scopes: List<ScopeReqDto>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리스트의 크기에 제한이 없는 것 같습니다. 이론상 수천개 이상의 리스트를 전송할 수 있는 것 같아요.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같은 scopeName으로 스코프를 중복 추가할 수 있는 검증이 없습니다. ThirdPartyScopeJpaRepository.findByApplicationIdAndScopeName()이 이미
존재하는데 활용되지 않고 있습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 클래스가 enum에서 클래스가 되며 equals(),hashCode() 메서드가 사라졌는데 내부적으로 이를 사용하는 JPA 등의 의존성이나 toSet() 과 같은 메서드의 반환값이 변동될 수 있을 것 같습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 클래스 역시 enum에서 클래스가 되며 equals(),hashCode() 메서드가 사라졌는데 내부적으로 이를 사용하는 JPA 등의 의존성이나 toSet() 과 같은 메서드의 반환값이 변동될 수 있을 것 같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement:개선작업 새 기능 또는 기존 코드 개선에 관한 내용입니다 waiting for review:검토 대기 확인을 대기하고 있습니다

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants