Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
- name: Checkout the code
uses: actions/checkout@v4

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'

- name: Setup Gradle
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dev-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
uses: actions/checkout@v4

# --- Java, Gradle 설정 ---
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/prod-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ jobs:
uses: actions/checkout@v4

# --- Java, Gradle 설정 ---
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# JDK 버전 설정
FROM eclipse-temurin:17-jdk
FROM eclipse-temurin:21-jdk

# JAR_FILE 변수 정의
ARG JAR_FILE=./build/libs/solid-connection-0.0.1-SNAPSHOT.jar
Expand Down
16 changes: 9 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.4'
id 'org.flywaydb.flyway' version '9.16.3'
id 'org.springframework.boot' version '3.5.11'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.flywaydb.flyway' version '10.15.0'
Comment on lines +3 to +5
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

What is the latest stable version of Spring Boot 3.5.x as of April 2026?

💡 Result:

The latest stable version of Spring Boot 3.5.x as of April 2, 2026, is 3.5.13.

Citations:


🌐 Web query:

Flyway 10.15.0 release notes breaking changes from 9.x to 10.x

💡 Result:

Flyway 10.15.0 has no breaking changes listed in its release notes. The breaking changes from Flyway 9.x to 10.x occurred primarily in Flyway 10.0.0 and subsequent minor releases within the 10.x series. Key breaking changes for the 9.x to 10.x upgrade: - Removed deprecated flyway.check.reportFilename; use flyway.reportFilename instead. Report generation is now off by default (enable with reportEnabled). - 'SQL' folder removed as a default location for migration scripts (bad practice; specify locations explicitly). - flyway.plugins parameter namespace retired (backwards compatible but deprecate usage; e.g., flyway.plugins.clean -> sqlserver.clean.mode). - Database modules separated from flyway-core into individual modules (update dependencies for affected databases). - For Redgate distribution: licenseKey, undoSqlMigrationPrefix, and cherryPick configurations moved. - cherryPick now requires a configuration extension object via plugin registry (API users). - License key logic moved to 'flyway-key-licensing' module. Flyway 10.15.0 (2024-06-11) notes only bug fixes and minor changes, no breaking changes. Sources: Official Redgate Flyway release notes, upgrade guide, and V10 announcement.

Citations:


🌐 Web query:

Spring dependency-management plugin 1.1.7 latest version

💡 Result:

The latest version of the Spring Dependency Management Plugin (io.spring.dependency-management) is 1.1.7, released on December 17, 2024. This is confirmed by the official Gradle Plugin Portal, Maven Central repositories, the plugin's documentation, and the GitHub releases page. As of 2026-04-02, no newer version is indicated in these authoritative sources.

Citations:


🏁 Script executed:

# Search for testcontainers and other key dependency versions in build.gradle
cat build.gradle | grep -E "version|testcontainers|flyway|spring" | head -30

Repository: solid-connection/solid-connect-server

Length of output: 1414


🌐 Web query:

Testcontainers 2.0.2 release latest version

💡 Result:

The latest version of Testcontainers for Java is 2.0.4, released on 2026-03-19. Testcontainers 2.0.2 was released on 2025-11-13. As of 2026-04-02, 2.0.4 is the most recent release, confirmed by the official documentation, GitHub releases, and Maven repositories.

Citations:


플러그인 버전 확인 및 업데이트 제안

확인 결과, 명시된 모든 버전이 실제로 존재하며 안정적입니다. 다만 다음과 같은 점들을 고려해 주세요:

  1. Spring Boot 3.5.11 → 3.5.13 업데이트 권장

    • 현재 사용 중인 3.5.11은 유효하지만, 최신 안정판 3.5.13이 릴리스되었습니다 (2패치 최신).
  2. Spring Dependency Management 1.1.7

    • 최신 안정판입니다 (2024년 12월 17일 릴리스). 유지해도 무방합니다.
  3. Flyway 10.15.0 마이그레이션 주의

    • Flyway 10.15.0 자체는 안정적이지만, 9.x에서 10.x로 업그레이드할 때 다음 변경 사항을 검토하세요:
      • 설정 파일 경로 변경 (예: flyway.plugins.* → database 모듈 설정)
      • SQL 마이그레이션 폴더 위치 명시 필요
      • 데이터베이스별 모듈 의존성 분리
  4. Testcontainers 2.0.2 → 2.0.4 업데이트 권장

    • 현재 2.0.2는 유효하지만, 최신 버전 2.0.4 (2026년 3월 19일 릴리스)로 업데이트 가능합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build.gradle` around lines 3 - 5, Update the plugin versions in build.gradle:
bump id 'org.springframework.boot' from '3.5.11' to '3.5.13' and keep id
'io.spring.dependency-management' at '1.1.7'; retain id 'org.flywaydb.flyway' at
'10.15.0' but ensure you apply Flyway 10.x migration changes (verify flyway
configuration keys, explicitly set SQL migration folder and separate
database-specific module dependencies) before committing; also consider updating
any Testcontainers dependency elsewhere in the project to 2.0.4 if present.

}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
sourceCompatibility = '21'
}

configurations {
Expand Down Expand Up @@ -43,8 +43,9 @@ dependencies {
// Security
implementation 'org.springframework.security:spring-security-config'
implementation 'org.springframework.security:spring-security-web'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
runtimeOnly 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359' // for jjwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

// Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
Expand All @@ -67,9 +68,10 @@ dependencies {
// Etc
implementation platform('software.amazon.awssdk:bom:2.41.4')
implementation 'software.amazon.awssdk:s3'
implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store:3.0.4'
implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store:3.4.2'
implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.apache.commons:commons-lang3'

// Database Proxy
implementation 'net.ttddyy.observation:datasource-micrometer:1.2.0'
Expand Down
4 changes: 2 additions & 2 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

Solid Connect Server는 교환학생 준비생을 위해 대학 정보, 멘토 매칭, 모의지원 기능 등을 제공하는 교환학생 지원 통합 플랫폼입니다.

- **언어**: Java 17
- **프레임워크**: Spring Boot 3.1.5
- **언어**: Java 21
- **프레임워크**: Spring Boot 3.5.11
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- **빌드 도구**: Gradle
- **데이터베이스**: MySQL (주), Redis (캐싱)
- **마이그레이션**: Flyway
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ private MultiValueMap<String, String> buildFormData(String code) {
private String parseEmailFromToken(PublicKey applePublicKey, String idToken) {
try {
return Jwts.parser()
.setSigningKey(applePublicKey)
.parseClaimsJws(idToken)
.getBody()
.verifyWith(applePublicKey)
.build()
.parseSignedClaims(idToken)
.getPayload()
.get("email", String.class);
} catch (Exception e) {
throw new CustomException(INVALID_APPLE_ID_TOKEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties;
import com.example.solidconnection.common.exception.CustomException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.annotation.PostConstruct;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Component;

/*
Expand Down Expand Up @@ -42,20 +41,19 @@ public String generateClientSecret() {
Date expiration = new Date(now.getTime() + TOKEN_DURATION);

return Jwts.builder()
.setHeaderParam("alg", "ES256")
.setHeaderParam(KEY_ID_HEADER, appleOAuthClientProperties.keyId())
.setSubject(appleOAuthClientProperties.clientId())
.setIssuer(appleOAuthClientProperties.teamId())
.setAudience(appleOAuthClientProperties.clientSecretAudienceUrl())
.setExpiration(expiration)
.signWith(SignatureAlgorithm.ES256, privateKey)
.header().add(KEY_ID_HEADER, appleOAuthClientProperties.keyId()).and()
.subject(appleOAuthClientProperties.clientId())
.issuer(appleOAuthClientProperties.teamId())
.audience().add(appleOAuthClientProperties.clientSecretAudienceUrl()).and()
.expiration(expiration)
.signWith(privateKey, Jwts.SIG.ES256)
.compact();
}

private PrivateKey loadPrivateKey() {
try {
String secretKey = appleOAuthClientProperties.secretKey();
byte[] encoded = Base64.decodeBase64(secretKey);
byte[] encoded = Base64.getMimeDecoder().decode(secretKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(keySpec);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
import com.example.solidconnection.auth.token.config.JwtProperties;
import com.example.solidconnection.common.exception.CustomException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Date;
import java.util.Map;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

Expand All @@ -32,16 +35,18 @@ public String generateToken(Subject subject, Map<String, String> customClaims, D
}

private String generateJwtTokenValue(String subject, Map<String, String> claims, Duration expireTime) {
Claims jwtClaims = Jwts.claims().setSubject(subject);
jwtClaims.putAll(claims);
Date now = new Date();
Date expiredDate = new Date(now.getTime() + expireTime.toMillis());
return Jwts.builder()
.setClaims(jwtClaims)
.setIssuedAt(now)
.setExpiration(expiredDate)
.signWith(SignatureAlgorithm.HS512, jwtProperties.secret())
.compact();
JwtBuilder builder = Jwts.builder()
.subject(subject)
.issuedAt(now)
.expiration(expiredDate);
claims.forEach(builder::claim);
return builder.signWith(getSigningKey()).compact();
}

private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtProperties.secret().getBytes(StandardCharsets.UTF_8));
}

@Override
Expand All @@ -61,9 +66,10 @@ public <T> T parseClaims(String token, String claimName, Class<T> claimType) {
private Claims parseJwtClaims(String token) {
try {
return Jwts.parser()
.setSigningKey(jwtProperties.secret())
.parseClaimsJws(token)
.getBody();
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
} catch (Exception e) {
throw new CustomException(INVALID_TOKEN);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.example.solidconnection.redis.RedisConstants.CREATE_CHANNEL;

import com.example.solidconnection.cache.CacheUpdateListener;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -49,7 +50,7 @@ public RedisTemplate<String, String> redisTemplate() {
public RedisTemplate<String, Object> objectRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(new ObjectMapper(), Object.class));
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.http.server.PathContainer;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

@Slf4j
@RequiredArgsConstructor
@Component
public class HttpLoggingFilter extends OncePerRequestFilter {

private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private static final List<String> EXCLUDE_PATTERNS = List.of("/actuator/**");
private static final PathPatternParser PATH_PATTERN_PARSER = new PathPatternParser();
private static final List<PathPattern> EXCLUDE_PATTERNS = List.of(
PATH_PATTERN_PARSER.parse("/actuator/**")
);
private static final List<String> EXCLUDE_QUERIES = List.of("token");
private static final String MASK_VALUE = "****";

Expand Down Expand Up @@ -60,9 +64,9 @@ protected void doFilterInternal(
}

private boolean isExcluded(HttpServletRequest req) {
String path = req.getRequestURI();
for (String p : EXCLUDE_PATTERNS) {
if (PATH_MATCHER.match(p, path)) {
PathContainer path = PathContainer.parsePath(req.getRequestURI());
for (PathPattern p : EXCLUDE_PATTERNS) {
if (p.matches(path)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import static com.example.solidconnection.score.domain.QLanguageTestScore.languageTestScore;
import static com.example.solidconnection.siteuser.domain.QSiteUser.siteUser;
import static io.jsonwebtoken.lang.Strings.hasText;
import static org.springframework.util.StringUtils.hasText;

import com.example.solidconnection.admin.dto.LanguageTestResponse;
import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse;
Expand Down
11 changes: 4 additions & 7 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ spring:
- optional:classpath:/config/application-variable.yml
- aws-parameterstore:/solid-connection/common/

tomcat:
threads:
min-spare: 20 # default 10

servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB

mvc:
path match:
matching-strategy: ANT_PATH_MATCHER
server:
tomcat:
threads:
min-spare: 20 # default 10

management:
endpoints:
Expand Down
5 changes: 0 additions & 5 deletions src/main/resources/config/application-cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@ spring:
config:
activate:
on-profile: local, dev, prod, loadtest

cloud:
aws:
stack:
auto: false
12 changes: 0 additions & 12 deletions src/main/resources/config/application-db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ spring:
ddl-auto: none
generate-ddl: false
show-sql: false
database: mysql
defer-datasource-initialization: false

datasource:
driverClassName: com.mysql.cj.jdbc.Driver

flyway:
enabled: true
locations: classpath:db/migration
Expand All @@ -31,12 +27,8 @@ spring:
ddl-auto: validate
generate-ddl: false
show-sql: false
database: mysql
defer-datasource-initialization: false

datasource:
driverClassName: com.mysql.cj.jdbc.Driver

flyway:
enabled: true
locations: classpath:db/migration
Expand All @@ -53,7 +45,6 @@ spring:
ddl-auto: create
generate-ddl: true
show-sql: true
database: mysql
defer-datasource-initialization: true
properties:
hibernate:
Expand All @@ -63,8 +54,5 @@ spring:
init:
mode: always

datasource:
driverClassName: com.mysql.cj.jdbc.Driver

flyway:
enabled: false
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

Expand All @@ -55,7 +55,7 @@ class AdminHostUniversityServiceTest {
@Autowired
private UnivApplyInfoFixtureBuilder univApplyInfoFixtureBuilder;

@SpyBean
@MockitoSpyBean
private CustomCacheManager cacheManager;

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
Expand All @@ -35,7 +35,7 @@ class RefreshTokenCookieManagerTest {
@Autowired
private TokenProperties tokenProperties;

@MockBean
@MockitoBean
private RefreshTokenCookieProperties refreshTokenCookieProperties;

private final String domain = "example.com";
Expand Down
Loading
Loading