Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 8 additions & 6 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'
}

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'
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
Expand All @@ -70,6 +71,7 @@ dependencies {
implementation 'io.awspring.cloud:spring-cloud-aws-starter-parameter-store:3.0.4'
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
- **빌드 도구**: 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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
import com.example.solidconnection.common.exception.ErrorCode;
import com.example.solidconnection.support.TestContainerSpringBootTest;
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.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -77,9 +80,10 @@ class 토큰을_생성한다 {

private Duration getActualExpireTime(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtProperties.secret())
.parseClaimsJws(token)
.getBody();
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return Duration.ofMillis(claims.getExpiration().getTime() - claims.getIssuedAt().getTime());
}
}
Expand Down Expand Up @@ -114,8 +118,7 @@ class 토큰으로부터_subject_를_추출한다 {
@Test
void subject_가_없는_토큰의_subject_를_추출하면_예외가_발생한다() {
// given
Claims claims = Jwts.claims(new HashMap<>());
String subjectNotExistingToken = createExpiredToken(claims);
String subjectNotExistingToken = createExpiredToken(new HashMap<>());
String subjectBlankToken = tokenProvider.generateToken(new Subject(" "), expectedExpireTime);

// when, then
Expand Down Expand Up @@ -155,8 +158,9 @@ class 토큰으로부터_claim_을_추출한다 {
@Test
void 유효하지_않은_토큰의_claim_을_추출하면_예외가_발생한다() {
// given
Claims expectedClaims = Jwts.claims(new HashMap<>(Map.of(claimKey, claimValue)));
String token = createExpiredToken(expectedClaims);
Map<String, Object> claims = new HashMap<>();
claims.put(claimKey, claimValue);
String token = createExpiredToken(claims);

// when
assertThatCode(() -> tokenProvider.parseClaims(token, claimKey, String.class))
Expand Down Expand Up @@ -184,19 +188,22 @@ class 토큰으로부터_claim_을_추출한다 {

private String createExpiredToken(String subject) {
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() - 1000))
.signWith(SignatureAlgorithm.HS256, jwtProperties.secret())
.subject(subject)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() - 1000))
.signWith(getSigningKey())
.compact();
}

private String createExpiredToken(Claims claims) {
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() - 1000))
.signWith(SignatureAlgorithm.HS256, jwtProperties.secret())
.compact();
private String createExpiredToken(Map<String, Object> claims) {
JwtBuilder builder = Jwts.builder()
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() - 1000));
claims.forEach(builder::claim);
return builder.signWith(getSigningKey()).compact();
}

private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtProperties.secret().getBytes(StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@DisplayName("OAuth 서비스 테스트")
@TestContainerSpringBootTest
Expand All @@ -30,7 +30,7 @@ class OAuthServiceTest {
@Autowired
private SiteUserFixture siteUserFixture;

@MockBean
@MockitoBean
private OAuthClientMap oauthClientMap;

private final AuthType authType = AuthType.KAKAO;
Expand Down
Loading
Loading