Skip to content
Merged
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
80 changes: 80 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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

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 (/ with retry)
run: |
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

1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 1 addition & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
app:
build: .
ports:
- "8080:8080"
- "127.0.0.1:8080:8080"
depends_on:
db:
condition: service_healthy
Expand All @@ -16,8 +16,6 @@ services:

db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_DATABASE=${DB_NAME}
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +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.auto-detect=false

3 changes: 2 additions & 1 deletion src/main/java/com/gdgoc/member/account/UserAuth.java
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

public interface UserAuthRepository extends JpaRepository<UserAuth, UUID> {
Optional<UserAuth> findByExternalUid(String externalUid);
}
Optional<UserAuth> findByEmail(String email);
}
15 changes: 7 additions & 8 deletions src/main/java/com/gdgoc/member/security/CurrentUserService.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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() {
Expand All @@ -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(),
Expand All @@ -42,4 +41,4 @@ public CurrentUser requireUser() {
userAuth.getRole()
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,17 +20,61 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.Optional;

@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();

Optional<UserAuth> userAuthOptional = userAuthRepository.findByExternalUid(oidcUser.getSubject());
UserAuth userAuth;

if (userAuthOptional.isPresent()) {
userAuth = userAuthOptional.get();
} else {
Optional<UserAuth> userAuthByEmailOptional = userAuthRepository.findByEmail(oidcUser.getEmail());
if (userAuthByEmailOptional.isPresent()) {
userAuth = userAuthByEmailOptional.get();
userAuth.setExternalUid(oidcUser.getSubject());
userAuthRepository.save(userAuth);
} else {
UserAuth newUser = new UserAuth(
UUID.randomUUID(),
oidcUser.getSubject(),
oidcUser.getEmail(),
null,
Role.MEMBER
);
userAuth = userAuthRepository.save(newUser);
}
}

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);
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/com/gdgoc/member/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) -> {
Expand Down Expand Up @@ -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
Expand All @@ -81,11 +87,15 @@ 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);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
Expand Down
Loading