JWT 기반 인증 시스템을 직접 서버리스 환경(AWS API Gateway + Lambda + ElastiCache + RDS)에서 구축한 경험을 정리
MSA 프로젝트시 각 서비스별로 JWT를 발급, 인증하는 것보다는 Api gateway를 이용하여 유저부분을 담당하게하여 각 서비스에 JWT를 주어 복잡도 감소시킴
제목이 서버리스인 이유는 딱 jwt 발급까지만 서버리스이기 때문. 그 후에 Spring Boot 연동 및 클라이언트부터 인증 처리, 데이터 저장
API Gateway| REST API 엔드포인트 제공, Lambda로 요청 프록시Lambda (Node.js)| 로그인/회원가입 처리, JWT 발급, Redis 저장ElastiCache (Redis)| Refresh Token 저장소 (7일 TTL)RDS (MySQL)| 사용자 정보 저장Spring Boot (Kotlin)| 백엔드 리소스 서버, JWT 기반 인가 처리❓ JWT 검증 위치
- Lambda
- Lambda Authorizer: Api gateway에서 Jwt를 검증하는 Lambda를 붙임
- API Gateway
- Cognito Authorizer: API Gateway에서 Cognito로 토큰 검증 위임
- Spring boot
- Spring security: 직접 JwtFilter를 설정하여 Jwt 검증
여기서는 검증을 백엔드에 위임했지만, 만약 백엔드 서버가 없다는 가정하에는 위 2개 중 하나가 좋은 방법.


리소스를 등록하고 스테이지로 배포를 해주면 완료

스테이지에 있는 URL 호출을 복사하여 경로를 붙여주어 직접 api 테스트

node, python, java 등 여러 언어로 만들기 가능.
복잡한 데이터처리는 python이 가진 데이터 라이브러리를 이용하여 사용하는게 좋겠지만, 간단한 테스트용으로는 node로 생성하는게 좋을 것 같다.
RDS와 ElastiCache를 연결하여 RDS에 있는 사용자정보를 불러와 JWT를 만들고 클라이언트에게 응답, 그리고 Redis에 리프레시 토큰을 저장하여 만료된 액세스 토큰 요청시 Lambda를 이용해 리프레시 토큰을 통해 엑세스 토큰 재발급
// db connect & get user id
const userId = ...
// JWT 발급
const accessToken = jwt.sign({ sub: userId }, process.env.JWT_SECRET, {
expiresIn: '15m',
});
const refreshToken = jwt.sign({ sub: userId }, process.env.JWT_SECRET, {
expiresIn: '1d',
});
// Redis에 Refresh Token 저장
await redis.set(`refresh:${userId}`, refreshToken, 'EX', 60 * 60 * 24 * 1);
jwt 인증처리
class JwtAuthFilter(
private val jwtProvider: JwtProvider // 토큰 검증 클래스
): OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val authHeader = request.getHeader("Authorization")
if (authHeader?.startsWith("Bearer ") == true) {
val token = authHeader.removePrefix("Bearer ")
if(jwtProvider.validateToken(token)) {
val userId = jwtProvider.getUserId(token)
// user id 인증 저장
val auth = UsernamePasswordAuthenticationToken(userId, null, emptyList())
// 인증 정보 저장
SecurityContextHolder.getContext().authentication = auth
}
}
filterChain.doFilter(request, response)
}
}
@GetMapping("/test")
fun test(principal: Principal): String =
"My ID = ${principal.name}"
principal로 클라이언트가 요청한 jwt를 확인 후 저장했던 subject를 이용하여 직접 DB 혹은 필요한 서비스에 연결
API Gateway + Lambda만으로도 인증 처리하여 간소화 된 서비스 운영이 가능해짐