Skip to content

Commit

Permalink
Merge 01dede0 into 1836a2d
Browse files Browse the repository at this point in the history
  • Loading branch information
pminsung12 authored Jul 23, 2024
2 parents 1836a2d + 01dede0 commit 0601154
Show file tree
Hide file tree
Showing 140 changed files with 3,361 additions and 1,260 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/dev-build-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
- dev

jobs:
build-and-test:
Expand Down Expand Up @@ -70,7 +72,7 @@ jobs:
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name == 'push'
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -109,11 +111,19 @@ jobs:
-e SPRING_DATASOURCE_URL=${{ secrets.DEV_DB_URL }} \
-e SPRING_DATASOURCE_USERNAME=${{ secrets.DEV_DB_USERNAME }} \
-e SPRING_DATASOURCE_PASSWORD=${{ secrets.DEV_DB_PASSWORD }} \
-e SPRING_JWT_SECRET=${{ secrets.JWT_SECRET }} \
-e OAUTH_CLIENTID=${{ secrets.KAKAO_CLIENT_ID }} \
-e OAUTH_KAUTHTOKENURLHOST=${{ secrets.KAUTH_TOKEN_URL_HOST }} \
-e OAUTH_KAUTHUSERURLHOST=${{ secrets.KAUTH_USER_URL_HOST }} \
-e NCP_OBJECT_STORAGE_ACCESS_KEY=${{ secrets.NCP_OBJECT_STORAGE_ACCESS_KEY }} \
-e NCP_OBJECT_STORAGE_SECRET_KEY=${{ secrets.NCP_OBJECT_STORAGE_SECRET_KEY }} \
-e TZ=Asia/Seoul \
${{ secrets.DOCKERHUB_USERNAME }}/spot-server:dev-${{ github.sha }}
docker system prune -af
create-release:
needs: [ build-and-test, deploy ] # deploy job이 성공적으로 완료된 후에만 실행
# if: github.event.pull_request.merged == true # PR이 merge된 경우에만 실행
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -133,3 +143,4 @@ jobs:
tag: ${{ steps.tag_version.outputs.new_tag }}
name: Release ${{ steps.tag_version.outputs.new_tag }}
body: ${{ steps.tag_version.outputs.changelog }}

4 changes: 4 additions & 0 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework:spring-aspects")
implementation("org.springframework.boot:spring-boot-starter-data-jpa") // pageable

// security
implementation("org.springframework.boot:spring-boot-starter-security")
Expand All @@ -23,6 +24,9 @@ dependencies {
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")

// aop
implementation("org.springframework.boot:spring-boot-starter-aop")

}

// spring boot main application이므로 실행 가능한 jar를 생성한다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package org.depromeet.spot.application.block.dto.response;

import java.util.List;

import org.depromeet.spot.usecase.port.in.block.BlockReadUsecase.RowInfo;

import lombok.Builder;

@Builder
public record RowInfoResponse(Long id, int number, int minSeatNum, int maxSeatNum) {
public record RowInfoResponse(Long id, int number, List<Integer> seatNumList) {

public static RowInfoResponse from(RowInfo rowInfo) {
return RowInfoResponse.builder()
.id(rowInfo.getId())
.number(rowInfo.getNumber())
.minSeatNum(rowInfo.getMinSeatNum())
.maxSeatNum(rowInfo.getMaxSeatNum())
.seatNumList(rowInfo.getSeatNumList())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.depromeet.spot.application.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 해당 어노테이션은 헤더에 담긴 Jwt 토큰에서 <b>memberId</b> 값을 가져오는 어노테이션입니다. <br>
* 컨트롤러에서 사용하려는 <b>메소드</b>에 사용하면됩니다. <br>
* <br>
* <b>파라미터</b>에 <b>Long memberId</b>를 추가해주어야 합니다. <br>
* <b>@Parameter(hidden = true) Long memberId</b>로 하시면 <br>
* 해당 파라미터는 Swagger에서 제외됩니다. :)
*/
@Target(ElementType.METHOD) // 적용 대상 : 메소드
@Retention(RetentionPolicy.RUNTIME) // 런타임에 사용할 수 있도록 설정
public @interface CurrentMember {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.depromeet.spot.application.common.aop;

import jakarta.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.depromeet.spot.application.common.jwt.JwtTokenUtil;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@Aspect
@RequiredArgsConstructor
@Slf4j
public class JwtTokenAspect {

private final JwtTokenUtil jwtTokenUtil;

private final HttpServletRequest request;

@Around("@annotation(org.depromeet.spot.application.common.annotation.CurrentMember)")
public Object getMemberIdFromTokenAspect(ProceedingJoinPoint joinPoint) throws Throwable {
Long memberId = jwtTokenUtil.getIdFromJWT(jwtTokenUtil.getAccessToken(request));

// 동작하는 메소드의 시그니처를 가져옴.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
Class<?>[] parameterTypes = signature.getParameterTypes();
String[] parameterNames = signature.getParameterNames();

for (int i = 0; i < args.length; i++) {
if ("memberId".equals(parameterNames[i]) && parameterTypes[i] == Long.class) {
args[i] = memberId; // memberId로 변경
}
}

// 변경된 인자로 메서드 실행
return joinPoint.proceed(args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ public class SecurityConfig {

private final JwtTokenUtil jwtTokenUtil;

private static final String[] AUTH_WHITELIST = {
"/api/**",
"/swagger-ui/**",
"/api-docs",
"swagger-ui-custom.html",
"/v3/api-docs/**",
"/api-docs/**",
"/swagger-ui.html",
"/favicon.ico/**",
"/api/v1/members/**"
};

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
Expand All @@ -31,9 +43,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests(
authorize ->
authorize
// 테스트, 개발 중엔 모든 경로 오픈.
.requestMatchers("/**")
.permitAll())
.requestMatchers(AUTH_WHITELIST)
.permitAll()
.anyRequest()
.authenticated())
// UsernamePasswordAuthenticationFilter 필터 전에 jwt 필터가 먼저 동작하도록함.
.addFilterBefore(
new JwtAuthenticationFilter(jwtTokenUtil),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@

@ComponentScan(basePackages = {"org.depromeet.spot.application"})
@Configuration
@Import(value = {UsecaseConfig.class, JpaConfig.class, NcpConfig.class})
@Import(value = {UsecaseConfig.class, JpaConfig.class, NcpConfig.class, SwaggerConfig.class})
public class SpotApplicationConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.depromeet.spot.application.common.config;

import java.util.List;

import jakarta.servlet.ServletContext;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.security.SecurityScheme.In;
import io.swagger.v3.oas.models.security.SecurityScheme.Type;
import io.swagger.v3.oas.models.servers.Server;
import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
public class SwaggerConfig {

@Bean
public OpenAPI openAPI(ServletContext servletContext) {
String contextPath = servletContext.getContextPath();
Server server = new Server().url(contextPath);
return new OpenAPI()
.servers(List.of(server))
.addSecurityItem(new SecurityRequirement().addList("accessToken"))
.components(authSetting())
.info(swaggerInfo());
}

private Info swaggerInfo() {
return new Info()
.version("v0.0.1")
.title("야구장 좌석 시야 서비스, SPOT API 문서")
.description("SPOT 서버의 API 문서입니다.");
}

private Components authSetting() {
return new Components()
.addSecuritySchemes(
"accessToken",
new SecurityScheme()
.type(Type.HTTP)
.scheme("Bearer")
.bearerFormat("JWT")
.in(In.HEADER)
.name("Authorization"));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.depromeet.spot.application.common.jwt;

import java.io.IOException;
import java.util.List;
import java.util.Arrays;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -26,41 +26,40 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenUtil jwtTokenUtil;

private static final String[] AUTH_WHITELIST = {
"/swagger-ui",
"/api-docs",
"swagger-ui-custom.html",
"/v3/api-docs",
"/api-docs",
"/swagger-ui.html",
"/favicon.ico",
"/api/v1/members"
};

@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

List<String> list =
List.of(
// swagger-ui와 v3/api-docs는 스웨거를 제외하기 위해 등록.
// 혹시나 스웨거 자원 사용 에러 발생 시 아래 두 가지 추가 필요함.
// Swagger UI에서 사용하는 외부 라ㅇ이브러리 제공 엔드포인트 : "/webjars/**"
// Swagger UI에서 사용하는 리소스 제공 엔드포인트 : "/swagger-resources/**"
// 로그인, 회원가입은 제외
"/swagger-ui", "/v3/api-docs", "/api/v1/members", "/kakao/", "/api/v1/");

// 현재 URL 이 LIST 안에 포함되있는걸로 시작하는가?
boolean flag = list.stream().anyMatch(url -> request.getRequestURI().startsWith(url));

if (flag) {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
final String requestURI = request.getRequestURI();
if (Arrays.stream(AUTH_WHITELIST).anyMatch(requestURI::startsWith)) {
filterChain.doFilter(request, response);
return;
}

String header = request.getHeader(HttpHeaders.AUTHORIZATION);
log.info("JwtAuthenticationFilter header : {}", header);

// header가 null이거나 빈 문자열이면 안됨.
if (header != null && !header.equalsIgnoreCase("")) {
if (header.startsWith("Bearer")) {
String access_token = header.split(" ")[1];
if (jwtTokenUtil.isValidateToken(access_token)) {
String memberId = jwtTokenUtil.getIdFromJWT(access_token);
MemberRole role = MemberRole.valueOf(jwtTokenUtil.getRoleFromJWT(access_token));
if (header.startsWith(JwtTokenEnums.BEARER.getValue())) {
String accessToken = header.split(" ")[1];
if (jwtTokenUtil.isValidateToken(accessToken)) {
Long memberId = jwtTokenUtil.getIdFromJWT(accessToken);
MemberRole role = MemberRole.valueOf(jwtTokenUtil.getRoleFromJWT(accessToken));
JwtToken jwtToken = new JwtToken(memberId, role);
SecurityContextHolder.getContext().setAuthentication(jwtToken);
filterChain.doFilter(request, response);
return;
}
}
// 토큰 검증 실패 -> Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
@Getter
@AllArgsConstructor
public class JwtToken implements Authentication {
// TODO : Authentication을 상속받고 UserDetail을 상속받은 커스텀 유저 정보 객체 생성해줘야함.
private String memberId;
private Long memberId;
private MemberRole memberRole;

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.depromeet.spot.application.common.jwt;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum JwtTokenEnums {
BEARER("Bearer");

private final String value;
}
Loading

0 comments on commit 0601154

Please sign in to comment.