Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

강원대 BE_권순호 6주차 과제(2단계) #398

Open
wants to merge 1 commit into
base: suno-boy
Choose a base branch
from
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
57 changes: 57 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Deploy to EC2

on:
push:
branches:
- main # main 브랜치에 푸시될 때 트리거

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: 코드 체크아웃
uses: actions/checkout@v2

- name: JDK 설정
uses: actions/setup-java@v3
with:
distribution: 'temurin' # JDK 배포판
java-version: '21' # 프로젝트에서 사용하는 JDK 버전

- name: Gradle 캐시 복원
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: 프로젝트 빌드
run: ./gradlew bootJar

- name: 배포 준비
run: |
mkdir -p deploy
cp build/libs/spring-gift-0.0.1-SNAPSHOT.jar deploy/

- name: EC2로 파일 전송
uses: appleboy/[email protected]
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
source: "deploy/spring-gift-0.0.1-SNAPSHOT.jar"
target: "/home/ubuntu/repository/"

- name: EC2에서 애플리케이션 재시작
uses: appleboy/[email protected]
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
sudo systemctl stop spring-gift
sudo systemctl start spring-gift
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,21 @@
- [x] 각 컨트롤러에 Swagger어노테이션 추가


---

## 2단계 - 배포하기

### 지금까지 만든 선물하기 서비스를 배포하고 클라이언트와 연동할 수 있어야 한다

- **요구 조건**
- 지속적인 배포를 위한 배포 스크립트를 작성한다.
- 클라이언트와 API 연동 시 발생하는 보안 문제에 대응한다.
- 서버와 클라이언트의 Origin이 달라 요청을 처리할 수 없는 경우를 해결한다.
- HTTPS는 필수는 아니지만 팀 내에서 논의하고 필요한 경우 적용한다.

- **구현 기능**
- [x] 배포 스크립트(deploy.yml작성)
- [x] 프론트팀의 요구사항대로 Request Body,파라미터, Responses등을 전면 변경


---
Expand Down
11 changes: 10 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ dependencies {
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0' // 업데이트된 버전
implementation 'org.hibernate.orm:hibernate-core:6.2.6.Final'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-json'
runtimeOnly 'com.h2database:h2'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
Expand All @@ -38,6 +39,14 @@ dependencies {
// SpringDoc을 사용하여 Spring Boot 3와의 호환성 유지
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'

annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'

}

tasks.named('test') {
Expand Down
68 changes: 34 additions & 34 deletions src/main/java/gift/Config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
package gift.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@Configuration
public class AppConfig {

@Bean
public MessageDigest passwordEncoder() throws NoSuchAlgorithmException {
return MessageDigest.getInstance("SHA-256");
}

// 추가적인 커스텀 메서드로 암호화를 구현할 수 있습니다.
public String encodePassword(String password) {
try {
MessageDigest digest = passwordEncoder();
byte[] hash = digest.digest(password.getBytes());
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
package gift.Config;//package gift.Config;
//
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import java.security.MessageDigest;
//import java.security.NoSuchAlgorithmException;
//
//@Configuration
//public class AppConfig {
//
// @Bean
// public MessageDigest passwordEncoder() throws NoSuchAlgorithmException {
// return MessageDigest.getInstance("SHA-256");
// }
//
// // 추가적인 커스텀 메서드로 암호화를 구현할 수 있다.
// public String encodePassword(String password) {
// try {
// MessageDigest digest = passwordEncoder();
// byte[] hash = digest.digest(password.getBytes());
// StringBuilder hexString = new StringBuilder(2 * hash.length);
// for (byte b : hash) {
// String hex = Integer.toHexString(0xff & b);
// if (hex.length() == 1) {
// hexString.append('0');
// }
// hexString.append(hex);
// }
// return hexString.toString();
// } catch (NoSuchAlgorithmException e) {
// throw new RuntimeException(e);
// }
// }
//}
92 changes: 46 additions & 46 deletions src/main/java/gift/Config/InterceptorOfToken.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
package gift.Config;

import gift.Service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class InterceptorOfToken implements HandlerInterceptor {

@Autowired
private UserService userService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String token = request.getHeader("Authorization");

if (token == null || token.isEmpty()) {
sendErrorResponse(response, "인증 헤더가 비었습니다.(토큰이 없습니다.)");
return false;
}

try {
if (userService.validateToken(token)) {
return true; // 요청을 계속 진행하여 사용자가 요청한 컨트롤러로 넘김.
} else {
sendErrorResponse(response, "유효하지 않은 토큰입니다.");
return false; // 요청에 false 응답을 줘서 사용자로 하여금 리프레쉬 토큰 요청이 오게 만듦. -> RefreshTokenController로 처리
}
} catch (Exception e) {
sendErrorResponse(response, "토큰 유효성 검증 실패: " + e.getMessage());
return false;
}
}

// 사용자에게 편리하게 json으로 예외 응답 처리. (추후에 Exception으로 따로 관리할 예정)
private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"" + message + "\"}");
}
}
package gift.Config;//package gift.Config;
//
//import gift.Service.UserService;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Component;
//import org.springframework.web.servlet.HandlerInterceptor;
//
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
//import java.io.IOException;
//
//@Component
//public class InterceptorOfToken implements HandlerInterceptor {
//
// @Autowired
// private UserService userService;
//
// @Override
// public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// String token = request.getHeader("Authorization");
//
// if (token == null || token.isEmpty()) {
// sendErrorResponse(response, "인증 헤더가 비었습니다.(토큰이 없습니다.)");
// return false;
// }
//
// try {
// if (userService.validateToken(token)) {
// return true; // 요청을 계속 진행하여 사용자가 요청한 컨트롤러로 넘김.
// } else {
// sendErrorResponse(response, "유효하지 않은 토큰입니다.");
// return false; // 요청에 false 응답을 줘서 사용자로 하여금 리프레쉬 토큰 요청이 오게 만듦. -> RefreshTokenController로 처리
// }
// } catch (Exception e) {
// sendErrorResponse(response, "토큰 유효성 검증 실패: " + e.getMessage());
// return false;
// }
// }
//
// // 사용자에게 편리하게 json으로 예외 응답 처리. (추후에 Exception으로 따로 관리할 예정)
// private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// response.setContentType("application/json");
// response.getWriter().write("{\"error\": \"" + message + "\"}");
// }
//}
72 changes: 36 additions & 36 deletions src/main/java/gift/Config/LoginUserArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
package gift.Config;

import gift.Exception.UnauthorizedException;
import gift.Service.UserService;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

private final UserService userService;

public LoginUserArgumentResolver(UserService userService) {
this.userService = userService;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(LoginUser.class) != null;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String token = webRequest.getHeader("인증 되었습니다.");
if (token != null && userService.validateToken(token)) {
return userService.getUserFromToken(token);
} else {
throw new UnauthorizedException("인증되지 않았습니다.");
}
}
}
package gift.Config;//package gift.Config;
//
//import gift.Exception.UnauthorizedException;
//import gift.Service.UserService;
//import org.springframework.core.MethodParameter;
//import org.springframework.stereotype.Component;
//import org.springframework.web.bind.support.WebDataBinderFactory;
//import org.springframework.web.context.request.NativeWebRequest;
//import org.springframework.web.method.support.HandlerMethodArgumentResolver;
//import org.springframework.web.method.support.ModelAndViewContainer;
//
//@Component
//public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
//
// private final UserService userService;
//
// public LoginUserArgumentResolver(UserService userService) {
// this.userService = userService;
// }
//
// @Override
// public boolean supportsParameter(MethodParameter parameter) {
// return parameter.getParameterAnnotation(LoginUser.class) != null;
// }
//
// @Override
// public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
// NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// String token = webRequest.getHeader("인증 되었습니다.");
// if (token != null && userService.validateToken(token)) {
// return userService.getUserFromToken(token);
// } else {
// throw new UnauthorizedException("인증되지 않았습니다.");
// }
// }
//}
15 changes: 15 additions & 0 deletions src/main/java/gift/Config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gift.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
14 changes: 14 additions & 0 deletions src/main/java/gift/Config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
package gift.Config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@OpenAPIDefinition(
// info = @Info(title = "Your API", version = "v1"),
security = @SecurityRequirement(name = "bearerAuth")
)
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
scheme = "bearer",
bearerFormat = "JWT"
)
@Configuration
public class SwaggerConfig {

Expand Down
Loading