프로젝트를 진행하면서 OAuth, JWT 방식으로 인증, 인가를 구현하였다.
우리 서버는 비즈니스 서버와 비디오 인코딩 서버가 존재했는데 두 서버 모두 요청시 사용자 인증이 필요했다.
비즈니스 서버에서는 JWT 를 검증하는 필터를 구현하여 JWT를 검증하였는데 이렇게 되면 MSA 환경에서 모든 서버에 JWT 검증 필터를 직접 구현해줘야 된다는 문제점이 있었다.
서비스에서 공통된 필터를 중복해서 구현하는 것은 비효율적이라고 판단하여
API Gateway 에서 인증 및 권한 처리를 중앙 집중식으로 처리하기로 하였다.
API Gateway와 Lambda 함수를 생성하고, Lambda Authorizer 를 설정한다.
특정 경로로의 요청은 Lambda Authorizer를 통하여 검증하고, 토큰에서 id 값을 추출하여 헤더에 실어 백엔드 서버로 보낸다.
클라이언트 애플리케이션과 통신하는 단일 진입점 역할
클라이언트는 여러 서비스의 엔드포인트를 직접 호출하는 대신 API Gateway 하나를 통해 서비스에 액세스한다.
MSA 환경에서 사실 필수적인 API Gateway 이다.
단일 진입점, 인증 및 권한 부여, 로드 밸런싱, 서비스 은폐, 모니터링 등 많은 이점을 가지고 있다.
서버를 프로비저닝하거나 관리하지 않고도 코드를 실행할 수 있게 해주는 컴퓨팅 서비스
Lambda 를 활용하면 서버리스 구조의 확장성 있는 권한 부여 로직을 구현할 수 있다.
AWS의 API Gateway 와 Lambda 를 사용하여 통합 인증을 하는 과정을 살펴보도록 하자.
리소스 이름에 이름을 입력하면 해당 경로의 리소스가 생성된다.
하위 모든 경로의 리소스를 생성하려면 프록시 리소스를 체크하면 된다.
우리의 서버는 이렇게 구성 되었다.
함수의 이름과 런타임 환경을 설정하고 생성을 하면 된다.
해당 람다 함수로 들어오면 다음과 같이 코드 입력을 할 수 있다.
우리는 람다함수를 JWT 토큰 검증 및 ID를 추출하여 헤더에 실어 보내도록 할것이다.
// index.js
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY
module.exports.handler = async (event,context, callback) => {
console.log(event);
const {awsRequestId} = context;
let {authorizationToken, methodArn} = event;
authorizationToken = authorizationToken.split(' ')[1];
try{
const token = jwt.verify(authorizationToken, secretKey);
console.log("token : ",token);
const userId = token.userId;
return generateAllow(awsRequestId, methodArn,userId);
}catch(err){
if(err.name === 'TokenExpiredError'){
console.log("TokenExpiredError");
callback("Unauthorized");
} else if(err.name === 'JsonWebTokenError'){
console.log("JsonWebTokenError");
return generateDeny(awsRequestId, methodArn);
}
}
};
const generateAllow = (principalId, methodArn, userId) => {
console.log("Allow")
return generatePolicy(principalId, 'Allow', methodArn, userId);
};
const generateDeny = (principalId, methodArn) => {
console.log(`[auth.js] deny. principalId: ${principalId}, resource: ${methodArn}`);
return generatePolicy(principalId, 'Deny', methodArn);
};
const generatePolicy = (principalId, effect, methodArn, userId) => {
const authResponse = {principalId};
if (effect && methodArn) {
const policyDocument = {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: methodArn,
Context: userId ? { userId: userId } : undefined
}]
};
authResponse.policyDocument = policyDocument;
}
console.log(authResponse);
return authResponse;
}
// package.json
{
"name": "jwt-authorizer",
"version": "1.0.0",
"description": "<!-- title: 'AWS Simple HTTP Endpoint example in NodeJS' description: 'This template demonstrates how to make a simple HTTP API with Node.js running on AWS Lambda and API Gateway using the Serverless Framework.' layout: Doc framework: v3 platform: AWS language: nodeJS authorLink: 'https://github.com/serverless' authorName: 'Serverless, inc.' authorAvatar: 'https://avatars1.githubusercontent.com/u/13742415?s=200&v=4' -->",
"main": "handler.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"jsonwebtoken": "^8.5.1"
},
"devDependencies": {
"serverless-dotenv-plugin": "^4.0.2",
"serverless-ignore": "^0.2.1"
}
}
이렇게 입력을 하고 TEST 를 하면 실패할것이다.
Lambda 함수가 jsonwebtoken
라이브러리를 사용하기 위해서는 Layer를 생성하여 모듈을 추가해 주어야 한다.
바탕화면에서 node.js 폴더를 생성하고 내부에 node_modules 폴더를 생성한다.
node.js 폴더에서 터미널을 열고 npm install jsonwebtoken
명령어를 실행하여 라이브러리를 설치한다.
이제 nodejs 폴더를 zip 파일로 압축하고 계층을 생성해보자
압축한 zip 파일을 업로드하고 람다와 동일한 환경의 런타임 환경을 선택하여 생성한다.
이렇게 하면 람다 설정은 끝이다.
이름을 입력하고 권한 부여자에서 사용할 Lambda 함수를 등록한다.
토큰소스에는 인증 토큰의 헤더를 입력하는데 우리는 클라이언트에서 Access Token을 헤더에 Authorization : Bearer ... 로 요청할 것 이기 때문에 Authorization을 입력했다.
권한 부여자를 생성하고 토큰값을 입력하고 테스트를 해보면 다음과 같이 Allow 응답을 받을 수 있다.
이를 CloudWatch에서 로그로도 확인할 수 있다.
권한 부여자를 설정하고 싶은 리소스에 진입하여 아까 생성한 권한부여자를 연결시켜준다.
아마 여기 까지 진행하고 테스트 하면 될것이다. 하지만 나는 CORS 에러가 발생했다.