기존 모놀리식 아키텍처에서는 JWT 기반 인증을 진행했었습니다.
서비스가 나뉜 지금도 각 서비스마다 시큐리티 필터를 거쳐 jwt 인증이 필요한데, 여기서 문제는 서비스마다 인증이 따로 된다는 점과 DB가 분리되어 인증에 필요한 유저 정보를 조회할 수가 없다는 점입니다.
이를 해결하기 위해 선택한 방법은, 각 서비스마다 인증 필터를 두는 것이 아닌 api gateway에서 pre filter로 인증/인가 작업을 진행하기로 결정하였습니다. 인증 서버까지 따로 분리하면 좋겠지만 아직은 MSA 아키텍처 전환 초기 셋팅이 완료되지 않아 이후에 분리할 예정이고, 현재는 project-service에서 인증 역할을 맡으려고 합니다.
목표 flow
사용자 요청 -> api gateway - pre filter에서 jwt 토큰 추출, project-service에 인증 및 유저 정보 조회 -> 유저 정보를 request에 추가하여, 다른 service에 요청 전달
1. api gateway pre filter 생성
각 서비스에 요청을 전달하기 전, pre filter를 거쳐 인증을 진행할 수 있도록 필터를 만들어 줍니다.
- 사용자 요청으로부터 받아온 쿠키에서 jwt 토큰 값들을 뽑아 인증 서버(project-service)로 전달합니다.
- 인증 서버로 부터 userId 값을 응답값으로 받아옵니다.
- userId 값을 request 헤더에 추가해 요청을 서비스 서버로 전달합니다.
@Component
public class AuthenticationFilter extends AbstractGatewayFilterFactory<AuthenticationFilter.Config> {
private final WebClient.Builder webClientBuilder;
AuthenticationFilter(WebClient.Builder webClientBuilder) {
super(Config.class);
this.webClientBuilder = webClientBuilder;
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
WebClient webClient = webClientBuilder.build();
try {
Integer response = webClient.get()
.uri("http://project-service/api/v1/auth")
.cookies(c -> {
if (cookies.containsKey("Authorization") && cookies.containsKey("RefreshToken")) {
c.add("Authorization", cookies.get("Authorization").get(0).getValue());
c.add("RefreshToken", cookies.get("RefreshToken").get(0).getValue());
}
})
.retrieve()
.bodyToMono(Integer.class)
.block();
request = exchange.getRequest().mutate()
.header("userId", response.toString())
.build();
return chain.filter(exchange.mutate().request(request).build());
} catch (Exception e) {
return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authentication failed", e));
}
};
}
@Getter
@Setter
public static class Config {
}
}
2. api gateway pre filter 연결
route 할 때, filter를 거쳐 라우팅될 수 있게 AuthenticationFilter를 연결해줍니다.
routes:
- id: region-service
uri: lb://region-service
predicates:
- Path=/api/v2/region/**
filters:
- AuthenticationFilter
3. 인증 서버 유저 정보 조회
- api gateway에서 인증 요청이 들어오면, Jwtfilter를 통해 쿠키에 있는 jwt 토큰을 검증합니다.
- jwt 토큰을 통해 유저를 식별하고 DB에서 존재하는 유저인지 확인한 후, userDetails에 유저 정보를 담습니다.
- controller에서 principal 유저 정보를 조회해 응답값으로 전달합니다.
@GetMapping("/auth")
public Integer checkAuth(@AuthenticationPrincipal CustomOAuth2User principal) {
if(principal.getUserDTO() !=null){
return principal.getUserDTO().getUserid();
}
return 0;
}
3. 서비스 연결 테스트
서비스에 요청이 들어오면, 헤더에서 userId 값을 추출해 유저가 지역별 방문한 횟수를 조회하는 API 테스트
@GetMapping("/visitCount")
public ResponseEntity<ApiResponse<RegionVisit>> fetchUserRegion(
@RequestHeader("userId") String userIdHeader
){
String userId = userIdHeader;
RegionVisit response = regionService.fetchUserCityDetailVisitCount(userId);
return ResponseEntity.ok(ApiResponse.success(response));
}
postman 테스트 결과 성공적 !
'댕댕어디가 프로젝트 > MSA' 카테고리의 다른 글
[댕댕어디가] MSA 아키텍처 전환기(8) - 서비스간 트랜잭션 (0) | 2025.01.15 |
---|---|
[댕댕어디가] MSA 아키텍처 전환기(6) - 서비스간 동기 통신(Spring Cloud Open Feign) (0) | 2025.01.12 |
[댕댕어디가] MSA 아키텍처 전환기(5) - 서비스간 비동기 통신 (kafka 연결) (0) | 2025.01.11 |
[댕댕어디가] MSA 아키텍처 전환기(4) - 서비스간 비동기 통신하기 (0) | 2025.01.08 |
[댕댕어디가] MSA 아키텍처 전환기(3) - API Gateway 구현 (0) | 2025.01.07 |