From 76b91fb8ca00d204923c2f7b482d2e0bae7003d9 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:07:02 +0000 Subject: [PATCH 01/17] fix: conform nginx --- .github/workflows/deploy-dev.yml | 68 ++++++++++++++++++++++++++++++++ docker-compose.yml | 4 +- 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..dc3ce80 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,68 @@ +name: Deploy (dev) to EC2 + +on: + push: + branches: ["dev"] + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + WORKDIR: . + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + cache: gradle + + - name: Build (Gradle) + working-directory: ${{ env.WORKDIR }} + run: | + chmod +x ./gradlew + ./gradlew clean build -x test + + - name: Pick jar + working-directory: ${{ env.WORKDIR }} + run: | + ls -al build/libs + # plain.jar / original.jar 등을 피해 "실행 가능한 jar"를 우선 선택 + JAR_PATH="$(ls build/libs/*.jar | grep -v -E '(plain|original)\.jar$' | head -n 1)" + if [ -z "$JAR_PATH" ]; then + echo "No runnable jar found in build/libs" + ls -al build/libs + exit 1 + fi + echo "JAR_PATH=$JAR_PATH" >> $GITHUB_ENV + echo "Selected: $JAR_PATH" + + - name: Configure SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts + + - name: Upload jar to EC2 + run: | + scp -i ~/.ssh/id_rsa \ + "$JAR_PATH" \ + "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/opt/member/app.jar" + + - name: Restart service + run: | + ssh -i ~/.ssh/id_rsa "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" \ + "sudo systemctl restart member && sudo systemctl status member --no-pager -l" + + - name: Health check (Swagger) + run: | + # HTTPS가 아니면 http:// 로 바꾸기 + curl -fsS "https://${{ secrets.EC2_HOST }}/swagger-ui/index.html" > /dev/null + echo "Health check OK" + diff --git a/docker-compose.yml b/docker-compose.yml index 1fd0b0c..ca4109c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: app: build: . ports: - - "8080:8080" + - "127.0.0.1:8080:8080" depends_on: db: condition: service_healthy @@ -16,8 +16,6 @@ services: db: image: mysql:8.0 - ports: - - "3306:3306" environment: - MYSQL_DATABASE=${DB_NAME} - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD} From 3fc4d7200fe49953173d94e64f7810a9a7f45d06 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:19:58 +0000 Subject: [PATCH 02/17] ci: add deploy workflow for dev --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index dc3ce80..ad77834 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -32,7 +32,7 @@ jobs: working-directory: ${{ env.WORKDIR }} run: | ls -al build/libs - # plain.jar / original.jar 등을 피해 "실행 가능한 jar"를 우선 선택 + JAR_PATH="$(ls build/libs/*.jar | grep -v -E '(plain|original)\.jar$' | head -n 1)" if [ -z "$JAR_PATH" ]; then echo "No runnable jar found in build/libs" From 465b62ca92a22447c9469aaaeaee9bac987474b8 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:26:18 +0000 Subject: [PATCH 03/17] fix: remove hardcoded org.gradle.java.home for CI --- gradle.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index adb8e0e..e07ed88 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.java.home=/usr/lib/jvm/java-17-amazon-corretto.x86_64 -org.gradle.java.installations.paths=/usr/lib/jvm/java-17-amazon-corretto.x86_64 +# github action의 내장 jdk 사용 +# org.gradle.java.installations.paths=/usr/lib/jvm/java-17-amazon-corretto.x86_64 org.gradle.java.installations.auto-detect=false From cf1170b3aa1b0e52ac43a81ac7776de64c1c90a0 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:35:12 +0000 Subject: [PATCH 04/17] fix: allow gradle to use runner JAVA_HOME --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index e07ed88..54d1aee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -org.gradle.java.home=/usr/lib/jvm/java-17-amazon-corretto.x86_64 +# org.gradle.java.home=/usr/lib/jvm/java-17-amazon-corretto.x86_64 # github action의 내장 jdk 사용 -# org.gradle.java.installations.paths=/usr/lib/jvm/java-17-amazon-corretto.x86_64 +org.gradle.java.installations.paths=/usr/lib/jvm/java-17-amazon-corretto.x86_64 org.gradle.java.installations.auto-detect=false From c106a3dc45d192b48430f562844ae6a21097f948 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:41:00 +0000 Subject: [PATCH 05/17] fix: deploy-dev.yml health check --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index ad77834..220c60f 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -63,6 +63,6 @@ jobs: - name: Health check (Swagger) run: | # HTTPS가 아니면 http:// 로 바꾸기 - curl -fsS "https://${{ secrets.EC2_HOST }}/swagger-ui/index.html" > /dev/null + curl -fsS "https://${{ secrets.EC2_HOST }}/actuator/health" > /dev/null echo "Health check OK" From 9e584f0106e7ecb7a79a5bff5a7330c0737e9c4b Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 09:49:56 +0000 Subject: [PATCH 06/17] fix: deploy-dev health check : / --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 220c60f..0bead7a 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -63,6 +63,6 @@ jobs: - name: Health check (Swagger) run: | # HTTPS가 아니면 http:// 로 바꾸기 - curl -fsS "https://${{ secrets.EC2_HOST }}/actuator/health" > /dev/null + curl -fsS "https://${{ secrets.EC2_HOST }}/" > /dev/null echo "Health check OK" From bc812e1fd5158cc5d812e50790f6bdbb8e89dfff Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Fri, 9 Jan 2026 10:11:47 +0000 Subject: [PATCH 07/17] fix: deploy-dev health check permitting delay --- .github/workflows/deploy-dev.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 0bead7a..f05219c 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -60,9 +60,21 @@ jobs: ssh -i ~/.ssh/id_rsa "${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}" \ "sudo systemctl restart member && sudo systemctl status member --no-pager -l" - - name: Health check (Swagger) + - name: Health check (/ with retry) run: | - # HTTPS가 아니면 http:// 로 바꾸기 - curl -fsS "https://${{ secrets.EC2_HOST }}/" > /dev/null - echo "Health check OK" + for i in {1..30}; do + code=$(curl -s -o /dev/null -w "%{http_code}" "https://${{ secrets.EC2_HOST }}/" || true) + echo "Attempt $i: HTTP $code" + + # 200 OK / 301-302 Redirect면 "살아있다"로 간주 + if [ "$code" = "200" ] || [ "$code" = "301" ] || [ "$code" = "302" ]; then + echo "Health check OK" + exit 0 + fi + + sleep 4 + done + + echo "Health check FAILED" + exit 1 From f8393ef6cfb4bc0959da26d12c6288a02f34acbd Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 21:25:46 +0900 Subject: [PATCH 08/17] fix: restore oauth success handler --- build.gradle | 1 + .../com/gdgoc/member/security/SecurityConfig.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 8dbda75..ef3ac9b 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ repositories { dependencies { // Web implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/src/main/java/com/gdgoc/member/security/SecurityConfig.java b/src/main/java/com/gdgoc/member/security/SecurityConfig.java index 4b853d4..9319bb9 100644 --- a/src/main/java/com/gdgoc/member/security/SecurityConfig.java +++ b/src/main/java/com/gdgoc/member/security/SecurityConfig.java @@ -4,6 +4,9 @@ import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; import com.gdgoc.member.BaseResponse; + +import lombok.RequiredArgsConstructor; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -19,12 +22,15 @@ @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final OAuth2SuccessHandler oAuth2SuccessHandler; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .cors(Customizer.withDefaults()) // CORS + .cors(Customizer.withDefaults()) // CORS .csrf(csrf -> csrf.disable()) .exceptionHandling(e -> e .authenticationEntryPoint((request, response, authException) -> { @@ -66,7 +72,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .oauth2Login(oauth2 -> oauth2 .loginPage("/oauth2/authorization/google") - .defaultSuccessUrl("/api/v1/me/account", true) + .successHandler(oAuth2SuccessHandler) ) .formLogin(form -> form.disable()) .logout(logout -> logout @@ -85,7 +91,7 @@ CorsConfigurationSource corsConfigurationSource() { config.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); - + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; From d4f39de15b5950b7f25ced1f2896764abbaf0e64 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 21:45:22 +0900 Subject: [PATCH 09/17] fix: make OAuth2 success handler to create user and profile on first login --- .../member/security/OAuth2SuccessHandler.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java index d7c23d3..c65a33d 100644 --- a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java +++ b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java @@ -1,7 +1,10 @@ package com.gdgoc.member.security; +import com.gdgoc.member.account.Role; import com.gdgoc.member.account.UserAuth; import com.gdgoc.member.account.UserAuthRepository; +import com.gdgoc.member.domain.profile.entity.Profile; +import com.gdgoc.member.domain.profile.repository.ProfileRepository; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -17,17 +20,45 @@ import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; +import java.util.UUID; @Component @RequiredArgsConstructor public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { private final UserAuthRepository userAuthRepository; + private final ProfileRepository profileRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OidcUser oidcUser = (OidcUser) authentication.getPrincipal(); - UserAuth userAuth = userAuthRepository.findByExternalUid(oidcUser.getName()).orElseThrow(); + UserAuth userAuth = userAuthRepository.findByExternalUid(oidcUser.getSubject()).orElseGet(() -> { + UserAuth newUser = new UserAuth( + UUID.randomUUID(), + oidcUser.getSubject(), + oidcUser.getEmail(), + null, + Role.MEMBER + ); + userAuthRepository.save(newUser); + + Profile newProfile = new Profile( + newUser.getUserId(), + oidcUser.getEmail(), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + profileRepository.save(newProfile); + return newUser; + }); ClassPathResource resource = new ClassPathResource("static/oauth_success.html"); Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8); From 61e9d8b7c62844a38faad63fac7e4985eab59a93 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 21:58:52 +0900 Subject: [PATCH 10/17] fix --- .../member/security/OAuth2SuccessHandler.java | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java index c65a33d..564becc 100644 --- a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java +++ b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java @@ -3,8 +3,6 @@ import com.gdgoc.member.account.Role; import com.gdgoc.member.account.UserAuth; import com.gdgoc.member.account.UserAuthRepository; -import com.gdgoc.member.domain.profile.entity.Profile; -import com.gdgoc.member.domain.profile.repository.ProfileRepository; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -27,7 +25,6 @@ public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { private final UserAuthRepository userAuthRepository; - private final ProfileRepository profileRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { @@ -40,24 +37,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo null, Role.MEMBER ); - userAuthRepository.save(newUser); - - Profile newProfile = new Profile( - newUser.getUserId(), - oidcUser.getEmail(), - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ); - profileRepository.save(newProfile); - return newUser; + return userAuthRepository.save(newUser); }); ClassPathResource resource = new ClassPathResource("static/oauth_success.html"); From 27e1c21a0d5b0a0b5127eaf63111ffcfe500de50 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 22:07:23 +0900 Subject: [PATCH 11/17] fix: handle OAuth2 user upsert with email too --- .../member/account/UserAuthRepository.java | 3 +- .../member/security/OAuth2SuccessHandler.java | 33 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/gdgoc/member/account/UserAuthRepository.java b/src/main/java/com/gdgoc/member/account/UserAuthRepository.java index 1869de5..dfba80d 100644 --- a/src/main/java/com/gdgoc/member/account/UserAuthRepository.java +++ b/src/main/java/com/gdgoc/member/account/UserAuthRepository.java @@ -7,4 +7,5 @@ public interface UserAuthRepository extends JpaRepository { Optional findByExternalUid(String externalUid); -} \ No newline at end of file + Optional findByEmail(String email); +} diff --git a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java index 564becc..ff9958f 100644 --- a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java +++ b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java @@ -19,6 +19,7 @@ import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.UUID; +import java.util.Optional; @Component @RequiredArgsConstructor @@ -29,16 +30,28 @@ public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OidcUser oidcUser = (OidcUser) authentication.getPrincipal(); - UserAuth userAuth = userAuthRepository.findByExternalUid(oidcUser.getSubject()).orElseGet(() -> { - UserAuth newUser = new UserAuth( - UUID.randomUUID(), - oidcUser.getSubject(), - oidcUser.getEmail(), - null, - Role.MEMBER - ); - return userAuthRepository.save(newUser); - }); + + Optional userAuthOptional = userAuthRepository.findByExternalUid(oidcUser.getSubject()); + UserAuth userAuth; + + if (userAuthOptional.isPresent()) { + userAuth = userAuthOptional.get(); + } else { + Optional userAuthByEmailOptional = userAuthRepository.findByEmail(oidcUser.getEmail()); + if (userAuthByEmailOptional.isPresent()) { + userAuth = userAuthByEmailOptional.get(); + userAuthRepository.save(new UserAuth(userAuth.getUserId(), oidcUser.getSubject(), userAuth.getEmail(), userAuth.getPasswordHash(), userAuth.getRole())); + } else { + UserAuth newUser = new UserAuth( + UUID.randomUUID(), + oidcUser.getSubject(), + oidcUser.getEmail(), + null, + Role.MEMBER + ); + userAuth = userAuthRepository.save(newUser); + } + } ClassPathResource resource = new ClassPathResource("static/oauth_success.html"); Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8); From 798b7d6aa1ba0fecae094ca34eb535069eea00b9 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 22:15:13 +0900 Subject: [PATCH 12/17] fix: update allowed origin URL --- src/main/resources/static/oauth_success.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/oauth_success.html b/src/main/resources/static/oauth_success.html index 924cd81..acdb97d 100644 --- a/src/main/resources/static/oauth_success.html +++ b/src/main/resources/static/oauth_success.html @@ -9,7 +9,7 @@ const allowedOrigins = [ "http://localhost:5173", "http://localhost:4173", - "https://gdgoc-seoultech.vercel.app/", + "https://gdgoc-seoultech.vercel.app", ]; const message = { From 5aaf02e678606cab9c11282b40120ac4b29ccb6b Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 22:46:02 +0900 Subject: [PATCH 13/17] fix: update allowed origins for CORS --- src/main/java/com/gdgoc/member/security/SecurityConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/gdgoc/member/security/SecurityConfig.java b/src/main/java/com/gdgoc/member/security/SecurityConfig.java index 9319bb9..961aec4 100644 --- a/src/main/java/com/gdgoc/member/security/SecurityConfig.java +++ b/src/main/java/com/gdgoc/member/security/SecurityConfig.java @@ -87,7 +87,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("https://gdgoc-seoultech.vercel.app")); + config.setAllowedOrigins(List.of( + "http://localhost:5173", + "http://localhost:4173", + "https://gdgoc-seoultech.vercel.app" + )); config.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); From 4feb2709083c16b020f85bf62667e431309a0a17 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 22:57:46 +0900 Subject: [PATCH 14/17] fix: session cookie --- src/main/resources/application.yaml | 138 ++++++++++++++-------------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3dc7ee5..ccedacd 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,77 +1,81 @@ spring: - application: - name: member + application: + name: member - mvc: - cors: - mappings: - "/api/**": - allowed-origins: "https://gdgoc-seoultech.vercel.app" - allowed-methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" - allowed-headers: "*" - allow-credentials: true + mvc: + cors: + mappings: + "/api/**": + allowed-origins: "https://gdgoc-seoultech.vercel.app" + allowed-methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS" + allowed-headers: "*" + allow-credentials: true + config: + import: "optional:file:.env[.properties]" + # ========================= + # AWS s3 + # ========================= + cloud: + aws: + credentials: + access-key: ${AWS_ACCESS_KEY:} + secret-key: ${AWS_SECRET_KEY:} + region: + static: ${AWS_S3_REGION:ap-northeast-2} + s3: + bucket: ${AWS_S3_BUCKET:} - config: - import: "optional:file:.env[.properties]" - # ========================= - # AWS s3 - # ========================= - cloud: - aws: - credentials: - access-key: ${AWS_ACCESS_KEY:} - secret-key: ${AWS_SECRET_KEY:} - region: - static: ${AWS_S3_REGION:ap-northeast-2} - s3: - bucket: ${AWS_S3_BUCKET:} + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB - servlet: - multipart: - max-file-size: 10MB - max-request-size: 10MB + # ========================= + # Security (Google OAuth2) + # ========================= + security: + oauth2: + client: + registration: + google: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + scope: + - openid + - profile + - email - # ========================= - # Security (Google OAuth2) - # ========================= - security: - oauth2: - client: - registration: - google: - client-id: ${GOOGLE_CLIENT_ID} - client-secret: ${GOOGLE_CLIENT_SECRET} - scope: - - openid - - profile - - email - - # ========================= - # Datasource - # ========================= - datasource: - url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:gdg_post}?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true - username: ${DB_USERNAME:root} - password: ${DB_PASSWORD:} - driver-class-name: com.mysql.cj.jdbc.Driver - # ========================= - # JPA - # ========================= - jpa: - hibernate: - ddl-auto: update - show-sql: true - properties: - hibernate: - dialect: org.hibernate.dialect.MySQLDialect - format_sql: true + # ========================= + # Datasource + # ========================= + datasource: + url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:gdg_post}?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true + username: ${DB_USERNAME:root} + password: ${DB_PASSWORD:} + driver-class-name: com.mysql.cj.jdbc.Driver + # ========================= + # JPA + # ========================= + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + format_sql: true server: - port: 8080 - address: 127.0.0.1 - forward-headers-strategy: framework + port: 8080 + address: 127.0.0.1 + forward-headers-strategy: framework + servlet: + session: + cookie: + same-site: none + secure: true logging: - level: - org.hibernate.SQL: debug + level: + org.hibernate.SQL: debug From 8f526425524b3748c19567a36638f2328bf0e98f Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 23:05:45 +0900 Subject: [PATCH 15/17] fix: add setter for external UID in UserAuth --- src/main/java/com/gdgoc/member/account/UserAuth.java | 3 ++- .../java/com/gdgoc/member/security/OAuth2SuccessHandler.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/gdgoc/member/account/UserAuth.java b/src/main/java/com/gdgoc/member/account/UserAuth.java index c8ff68b..8d54fc3 100644 --- a/src/main/java/com/gdgoc/member/account/UserAuth.java +++ b/src/main/java/com/gdgoc/member/account/UserAuth.java @@ -50,7 +50,8 @@ public UserAuth(UUID userId, String externalUid, String email, String passwordHa public String getPasswordHash() { return passwordHash; } public Role getRole() { return role; } + public void setExternalUid(String externalUid) { this.externalUid = externalUid; } public void setEmail(String email) { this.email = email; } public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; } public void setRole(Role role) { this.role = role; } -} \ No newline at end of file +} diff --git a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java index ff9958f..8ea8585 100644 --- a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java +++ b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java @@ -40,7 +40,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo Optional userAuthByEmailOptional = userAuthRepository.findByEmail(oidcUser.getEmail()); if (userAuthByEmailOptional.isPresent()) { userAuth = userAuthByEmailOptional.get(); - userAuthRepository.save(new UserAuth(userAuth.getUserId(), oidcUser.getSubject(), userAuth.getEmail(), userAuth.getPasswordHash(), userAuth.getRole())); + userAuth.setExternalUid(oidcUser.getSubject()); + userAuthRepository.save(userAuth); } else { UserAuth newUser = new UserAuth( UUID.randomUUID(), From 880d65a23ec8d9e0338438cbcb1494b041d686e8 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Fri, 9 Jan 2026 23:18:32 +0900 Subject: [PATCH 16/17] fix: prevent concurrent login --- .../gdgoc/member/security/CurrentUserService.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/gdgoc/member/security/CurrentUserService.java b/src/main/java/com/gdgoc/member/security/CurrentUserService.java index 0a63f5e..2afcea0 100644 --- a/src/main/java/com/gdgoc/member/security/CurrentUserService.java +++ b/src/main/java/com/gdgoc/member/security/CurrentUserService.java @@ -1,7 +1,7 @@ package com.gdgoc.member.security; -import com.gdgoc.member.account.AccountService; import com.gdgoc.member.account.UserAuth; +import com.gdgoc.member.account.UserAuthRepository; import com.gdgoc.member.global.error.UnauthorizedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -11,10 +11,10 @@ @Service public class CurrentUserService { - private final AccountService accountService; + private final UserAuthRepository userAuthRepository; - public CurrentUserService(AccountService accountService) { - this.accountService = accountService; + public CurrentUserService(UserAuthRepository userAuthRepository) { + this.userAuthRepository = userAuthRepository; } public CurrentUser requireUser() { @@ -29,10 +29,9 @@ public CurrentUser requireUser() { } String subject = oidcUser.getSubject(); - String email = oidcUser.getEmail(); - String externalUid = "google:" + subject; - UserAuth userAuth = accountService.getOrCreate(externalUid, email); + UserAuth userAuth = userAuthRepository.findByExternalUid(subject) + .orElseThrow(() -> new IllegalStateException("User not found: " + subject)); return new CurrentUser( userAuth.getUserId(), @@ -42,4 +41,4 @@ public CurrentUser requireUser() { userAuth.getRole() ); } -} \ No newline at end of file +} From b54fa5c0b59b494b2dd5fda93df98da5761432d0 Mon Sep 17 00:00:00 2001 From: Dohyeop Lim Date: Sat, 10 Jan 2026 17:06:22 +0900 Subject: [PATCH 17/17] feat: create empty profile on login --- .../member/security/OAuth2SuccessHandler.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java index 8ea8585..dbeab76 100644 --- a/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java +++ b/src/main/java/com/gdgoc/member/security/OAuth2SuccessHandler.java @@ -3,6 +3,8 @@ import com.gdgoc.member.account.Role; import com.gdgoc.member.account.UserAuth; import com.gdgoc.member.account.UserAuthRepository; +import com.gdgoc.member.domain.profile.entity.Profile; +import com.gdgoc.member.domain.profile.repository.ProfileRepository; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -26,6 +28,7 @@ public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { private final UserAuthRepository userAuthRepository; + private final ProfileRepository profileRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { @@ -54,6 +57,25 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo } } + profileRepository.findByUserId(userAuth.getUserId()).or(() -> { + Profile newProfile = new Profile( + userAuth.getUserId(), + oidcUser.getEmail(), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + profileRepository.save(newProfile); + return Optional.of(newProfile); + }); + ClassPathResource resource = new ClassPathResource("static/oauth_success.html"); Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8); String htmlContent = FileCopyUtils.copyToString(reader);