Skip to content
Open
Show file tree
Hide file tree
Changes from all 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'
}

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
6 changes: 3 additions & 3 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 Expand Up @@ -167,7 +167,7 @@ public class UserCreateResponse { ... }

### Core Framework

- **Spring Boot 3.1.5**: 스프링 부트
- **Spring Boot 3.5.11**: 스프링 부트
- **Spring Security**: JWT 기반 인증
- **Spring Data JPA**: ORM
- **QueryDSL**: 동적 쿼리 생성
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
Loading
Loading