practice-jwt-on-api-gateway
✅ 설계
API Gateway로 들어오는 요청에 대해 비즈니스 로직을 처리할 수 있는 Filter 기능을 가지고 있다. 그래서 이 Filter 기능을 활용해 아래와 같이 JWT 인증 로직을 구현하고자 한다.
✅ API Gateway에 JWT 인증 로직 구현하기
- JWT 의존성 추가하기
build.gradle
...
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway-server-webflux'
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
...
- application.yml 수정하기
application.yml
...
jwt:
secret: jscode-secret-1234-1234-1234-1234
- JwtAuthenticationFilter
@Component
public class JwtAuthenticationFilter extends AbstractGatewayFilterFactory {
@Value("${jwt.secret}")
private String jwtSecret;
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
// Reqeuest Header에서 토큰 가져오기
String token = exchange.getRequest()
.getHeaders()
.getFirst("Authorization");
System.out.println("토큰 : " + token);
// 토큰이 없을 경우 401 Unauthorized로 응답
if (token == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// SecretKey로 토큰 검증 및 Payload(userId 담겨있음) 가져오기
SecretKey secretKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
String subject = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
System.out.println("userId : " + subject);
// Payload를 X-User-Id 헤더에 담아서 Request 전달
// (= 다른 마이크로서비스에 요청 전달할 때 userId 정보를 담아서 보냄)
return chain.filter(
exchange.mutate()
.request(
exchange.getRequest()
.mutate()
.header("X-User-Id", subject)
.build()
)
.build()
);
};
}
}
참고) JWT 토큰 검증 로직을 더 자세히 작성할 수도 있지만 프로젝트의 심플함을 위해 간단하게 작성했다.
✅ 라우팅 설정하기
외부 클라이언트가 사용하는 API는 아래의 API 밖에 없다. 이 중에서 JWT 인증을 거쳐야 하는 API는 게시글 생성 API 밖에 없다. 게시글 생성 API에만 JwtAuthenticationFilter를 적용시켜보자.
[사용자 서비스]
- 회원가입 (
POST /users/sign-up) - 외부용 API
- 로그인 (
POST /users/login) - 외부용 API
[게시글 서비스]
- 게시글 생성 (
POST /boards) - 외부용 API
- 특정 게시글 조회 (
GET /boards/{boardId}) - 외부용 API
- 전체 게시글 조회 (
GET /boards) - 외부용 API
- application.yml 코드 수정하기
application.yml
server:
port: 8000
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: user-service
uri: http://localhost:8080
predicates:
- Path=/api/users/**
- id: board-service-jwt
uri: http://localhost:8081
predicates:
- Path=/api/boards
- Method=POST
filters:
- JwtAuthenticationFilter
- id: board-service
uri: http://localhost:8081
predicates:
- Path=/api/boards/**
jwt:
secret: jscode-secret-1234-1234-1234-1234
- 주의) routes에서 설정한 순서대로 처리하기 때문에
board-service 위에 board-service-jwt에 대한 설정값을 작성해야 한다.
- 서버 다시 실행시키기
- Header에 토큰 안 넣고, 게시글 작성 API 요청 보내보기
API Gateway의 인증 로직에 따라 요청이 잘 가로막혔다.
- Header에 토큰 넣고 요청 보내보기
- 로그인 API로 토큰 발급받기
- 발급받은 토큰을 Header에 넣고, 게시글 작성 API 요청 보내보기
정상적으로 게시글 작성이 된 걸 확인할 수 있다.