일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 프로그래머스 이중우선순위큐
- DesignPattern
- 파이썬
- JavaScript
- Spring multimodule
- 취업리부트코스
- 커스텀 헤더
- 개발자부트캠프추천
- 디자인패턴
- 구글 OAuth login
- TiL
- 디자인 패턴
- 1주일회고
- 코딩테스트 준비
- 빈 충돌
- jwt
- Python
- spring batch 5.0
- 단기개발자코스
- 전략패턴 #StrategyPattern #디자인패턴
- @FeignClient
- 항해99
- 프로그래머스
- jwttoken
- 빈 조회 2개 이상
- infcon 2024
- 99클럽
- 인프콘 2024
- KPT회고
- 개발자 취업
- Today
- Total
m1ndy5's coding blog
Ditto 프로젝트 멀티 모듈 도입 3편 - Gateway 도입하기 본문
Ditto 프로젝트 멀티 모듈 도입 3편 - Gateway 도입하기
정민됴 2024. 2. 6. 17:38기존 모놀리식 구조에서 멀티모듈 구조로 변경을 하니 문제 아닌 문제가 한가지 발생했다.
바로 각 모듈의 포트번호가 다르다는 것!
예를 들면 user 모듈에 있는 api들은 8080, activity 모듈에 있는 api들은 8081, newsfeed 모듈에 있는 api들은 8082에서 돌고 있었기 때문에 각 api들의 요청에 따라 포트번호를 변경해 요청해줘야했고 여간 귀찮은 일이 아니었다!!
그래서 이를 해결하기 위에 spring cloud gateway를 도입해 특정 포트로 요청이 들어오게 되면 각 api를 알맞은 포트로 연결해주는 역할을 하도록 했다.
spring cloud gateway를 사용하려면 아래와 같은 의존성을 추가해주면 된다.
이 또한 스프링 부트의 버전에 따라 맞춰서 넣자!!
implementation 'org.springframework.cloud:spring-cloud-starter-gateway:4.1.0'
이 뒤에 gateway의 port번호를 나같은 경우에는 8083으로 설정해주었다.
이제 무엇을 할 것이냐!!
바로 jwt 토큰을 검증하는 필터를 게이트웨이에 요청이 들어올 때 거치게 할것이다!!
본격적으로 gateway filter를 만들어보자
AuthFilter
jwt 토큰의 유효성을 검증하는 필터고 이 필터에서 Claims을 파싱하여 memberId를 헤더에 달아둘 것이다.
@RefreshScope
@Component
@RequiredArgsConstructor
public class AuthFilter implements GatewayFilter {
private final JwtTokenProvider jwtTokenProvider;
private final RouterValidator routerValidator;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (routerValidator.isSecured.test(request)){
if (this.isAuthMissing(request)) {
throw new IllegalArgumentException();
}
String token = this.getAuthHeader(request);
token = token.substring(7);
if (!jwtTokenProvider.validateToken(token)) {
throw new IllegalArgumentException();
}
this.updateRequest(exchange, token);
}
return chain.filter(exchange);
}
private String getAuthHeader(ServerHttpRequest request) {
return request.getHeaders().getOrEmpty("Authorization").get(0);
}
private boolean isAuthMissing(ServerHttpRequest request) {
return !request.getHeaders().containsKey("Authorization");
}
private void updateRequest(ServerWebExchange exchange, String token) {
Claims claims = jwtTokenProvider.parseClaims(token);
exchange.getRequest().mutate()
.header("memberId", String.valueOf(claims.get("memberId")))
.build();
}
}
GatewayFilter를 implements 받아 filter method를 오버라이딩했다.
해당 filter 메서드에서는 인가가 필요한 요청인지 확인후에 토큰이 유효한지 확인 후 유효하다면 Claims에서 memberId를 추출해 헤더에 memberId를 넣어 다음으로 보내는 작업을 하고 있다.
인가가 필요하지 않은 요청들은 RouterValidator를 사용해서 로직을 굳이 타지 않게 했다.
RouterValidator
package org.example;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.function.Predicate;
@Component
public class RouterValidator {
public static final List<String> openApiEndpoints = List.of(
"/api/members", "/api/auth/email-authentication", "/api/auth/authentication-code", "/api/auth/login",
"/api/follow/{toMemberId}/followings", "/api/follow/{toMemberId}/followers"
);
public Predicate<ServerHttpRequest> isSecured =
request -> openApiEndpoints
.stream()
.noneMatch(uri -> request.getURI().getPath().contains(uri));
}
openApiEndpoints에 있는 api들에 match가 되는지 여부를 확인하는 메서드를 가지고 있다.
GatewayConfig
다음으로는 어떤 요청이 어떤 포트로 갈 것인지 연결을 해주는 GatewayConfig를 만들었다.
package org.example;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class GatewayConfig {
private final AuthFilter authFilter;
@Bean
public RouteLocator ms1Route(RouteLocatorBuilder builder) {
return builder.routes()
.route("userAPI", r -> r.path("/api/members/**", "/api/auth/**", "/api/mypage/**")
.filters(f -> f.filter(authFilter))
.uri("http://localhost:8080")
)
.route("activityAPI", r -> r.path("/api/posts/**", "/api/follow/**")
.filters(f -> f.filter(authFilter))
.uri("http://localhost:8081")
)
.route("newsfeedAPI", r -> r.path("/api/newsfeed/**")
.filters(f -> f.filter(authFilter))
.uri("http://localhost:8082")
)
.build();
}
}
이 때 각각의 요청들이 필터를 거쳐야 들어올 수 있게 설정하였다.
각 인가된 memberId의 정보는 헤더에 담겨 있기 때문에
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/posts/{postId}/comments")
public class CommentController {
private final CommentService commentService;
@PostMapping
public BaseResponse<CommentResponse> uploadComment(@RequestHeader("memberId") Long memberId, @PathVariable("postId") Long postId, @Valid @RequestBody CommentRequest commentRequest){
return new BaseResponse<>(commentService.uploadComment(memberId, postId, commentRequest));
}
@GetMapping()
public BaseResponse<List<CommentResponse>> getComments(@PathVariable("postId") Long postId){
return new BaseResponse<>(commentService.getComments(postId));
}
@PatchMapping("/{commentId}")
public BaseResponse<CommentResponse> updateComment(@RequestHeader("memberId") Long memberId, @PathVariable("postId") Long postId, @PathVariable("commentId") Long commentId, @Valid @RequestBody CommentRequest commentRequest){
return new BaseResponse<>(commentService.updateComment(memberId, postId, commentId, commentRequest));
}
@DeleteMapping("/{commentId}")
public BaseResponse<CommentResponse> deleteComment(@RequestHeader("memberId") Long memberId, @PathVariable("postId") Long postId, @PathVariable("commentId") Long commentId){
return new BaseResponse<>(commentService.deleteComment(memberId, postId, commentId));
}
@PostMapping("/{commentId}/like")
public BaseResponse<CommentLikeResponse> pushCommentLike(@RequestHeader("memberId") Long memberId, @PathVariable("postId") Long postId, @PathVariable("commentId") Long commentId){
return new BaseResponse<>(commentService.pushCommentLike(memberId, postId, commentId));
}
}
이렇게 헤더에서 꺼내쓰면 된다!
기존 모놀리식 구조에서 멀티 모듈로 변환하면서 설계에 대해서 많은 고민과 헤맨 시간들을 통해 설계의 중요성과 필요성에 대해 느낄 수 있었고 어렴풋이 알고 있었던 개념들을 실제로 적용해가면서 ddd, msa, 멀티 모듈의 차이와 각각의 장단점을 몸소 느낄 수 있었던 경험이었다.
'Toy Projects > Ditto - Discuss Today's Topic' 카테고리의 다른 글
Ditto Project Spring Batch&Scheduling 2편 - Docker, Jenkins 사용하여 Spring Batch Job 실행하기 (1) | 2024.02.17 |
---|---|
Ditto Project Spring Batch&Scheduler 1편 - 스프링 배치 5.0, 스케줄러 적용하기 (0) | 2024.02.16 |
Ditto 프로젝트 멀티 모듈 도입 2편 - Entity 연관관계 끊기 (0) | 2024.02.06 |
Ditto 프로젝트 멀티 모듈 도입 1편 - 모듈 나누기에 대한 고민 (1) | 2024.02.02 |
DDD? MSA? 멀티 모듈? (0) | 2024.01.31 |