API Gateway의 권한 부여자(Authorizer)를 이용한 권한 부여(P139)
API Gateway의 권한 부여자를 이용해서 JWT 토큰을 검증해서 유효한 토큰인 경우에만 사용자 프로필을 조회하는 람다 함수를 실행할 수 있는 권한을 부여
참고 ⇒ https://auth0.com/docs/tokens/json-web-tokens/validate-json-web-tokens

#1 JWT 토큰을 검증하는 custom-authorizer 람다 함수를 생성
#1-1 람다 함수 생성


#1-2 작업 디렉터리 생성 및 필요 모듈 설치
PS C:\Users\i> cd C:\serverless\
PS C:\serverless> mkdir custom-authorizer
PS C:\serverless> cd .\custom-authorizer\
PS C:\serverless\custom-authorizer> npm init -y
PS C:\serverless\custom-authorizer> npm install jsonwebtoken
#1-3 package.json 파일에 predeploy, deploy 스크립트를 추가
{
"name": "custom-authorizer",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"predeploy": "del Lambda-Deployment.zip & zip -r Lambda-Deployment.zip * -x *.zip *.log node_modules/aws-sdk/*",
"deploy": "aws lambda update-function-code --function-name custom-authorizer-람다함수ARN --zip-file fileb://Lambda-Deployment.zip"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"jsonwebtoken": "^8.5.1"
}
}

#1-4 람다 함수 구현 ⇒ c:\serverless\custom-authorizer\index.js
// P141
'use strict';
var jwt = require('jsonwebtoken');
// generatePolicy 함수를 정의
/* 정책 문서 형식을 생성
{
"principalId": "...",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
}
*/
var generatePolicy = function (principalId, effect, resource) {
var authResponse = {}; // authResponse 객체 선언
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // 정책 문서 형식 버전
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
};
// 핸들러 함수를 정의
exports.handler = function(event, context, callback) {
if (!event.authorizationToken) {
callback('Could not find authorizationToken');
return;
}
// JWT 토큰의 앞부분(Bearer)을 제거
// "authorizationToken": "Bearer eyJhbGciOiJ~~~cCI6IkpXVCJ9.eyJnaXZlbl~~~pKNDQuZSJ9.mioxKcb1~~~W1LTk5_anGo"
var token = event.authorizationToken.split(' ')[1];
// auth0.com에서 제공한 Client Secret을 환경변수로부터 읽어와서 변수에 할당
var secretBuffer = new Buffer(process.env.AUTH0_SECRET);
// JWT 토큰을 검증
jwt.verify(token, secretBuffer, function(err, decoded) {
if (err) {
console.log('Failed jwt verification: ', err, 'auth:', event.authorizationToken);
callback('Authorization Failed');
} else {
var policy = generatePolicy('user', 'allow', event.methodArn);
console.log(policy);
callback(null, policy);
}
});
};
#1-5 람다 함수 배포
PS C:\serverless\custom-authorizer> npm run deploy

#1-6 람다 함수 실행에 필요한 환경 변수를 설정


#1-7 람다 함수 테스트

로컬 스토리지에 저장된 idToken 값에 "Bearer "를 추가한 값을 "authorizationToken" 값으로 설정
{
"authorizationToken": "Bearer xxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyy.zzzzzzzzzzzzzzzzzzz"
}


#1-8 https://jwt.io 사이트를 통해서 JWT 토큰을 수작업으로 검증

#1-9 auth0.com 서명에서 사용하는 알고리즘을 변경

:


Specify the algorithm used to sign the JsonWebToken:
- HS256: JWT will be signed with your client secret. ⇐ 비밀키 암호화 방식
- RS256: JWT will be signed with your private signing key and they can be verified using your public signing key. ⇐ 공개키 암호화 방식
#1-10 다시 로그인해서 발급받은 idToken을 추출해서 테스트

#1-11 API Gateway에 권한 부여자를 생성



#1-12 사용자 정의 권한 부여자를 /user-profile - GET 메서드에 연결
주의: API Gateway 대시보드로 이동했다가 리소스 메뉴로 이동



#1-13 로그인 후 프로필 버튼을 클릭했을 때 사용자 정보가 콘솔에 출력되는지 확인
요청/응답 헤더

브라우저 콘솔 로그

user-profile 로그 그룹이 생성 ⇒ user-profile 람다 함수가 호출(실행)되었음

custom-authorizer 로그

user-profile 로그

#1-14 개발자 도구를 이용해서 idToken 값을 수정(조작)했을 때 오류가 출력되는 것을 확인
요청/응답 헤더 ⇒ 4xx번 대 오류와 5xx번 대 오류가 발생하는 경우 CORS 관련 헤더를 설정하지 않음

브라우저 콘솔 로그

CloudWatch에 user-profile 로그 그룹이 생성되지 않음 ⇒ user-profile 람다 함수가 호출(실행)되지 않음

customer-authorizer 람다 함수의 로그


#1-15 4xx번 대 오류와 5xx번 대 오류가 발생해다 CORS 관련 헤더를 설정하도록 CORS 설정






S3 버킷에 저장된 파일 목록을 반환하는 람다 함수를 생성, 배포 (P166)
CLI 환경에서 람다 함수를 생성하고, 배포 ⇒ CreateFunction 권한이 필요
#1 Lambda-DevOps 그룹에 CreateFunction 권한을 추가


#2 프로젝트 환경 구성
#2-1 작업 디렉터리 생성 및 프로젝트 초기화
PS C:\serverless\custom-authorizer> cd C:\serverless\
PS C:\serverless> mkdir get-video-list
PS C:\serverless> cd .\get-video-list\
PS C:\serverless\get-video-list> npm init -y
#2-2 필요 모듈 추가
PS C:\serverless\get-video-list> npm install aws-sdk
PS C:\serverless\get-video-list> npm install async
참고 ⇒ https://www.npmjs.com/package/async
#2-3 package.json 파일에 create, precreate 스크립트를 추가
{
"name": "get-video-list",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"create": "aws lambda create-function --function-name get-video-list --handler index.handler --memory-size 128 --runtime nodejs4.3 --role 본인의_lambda-s3-execution-role_ARN --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip",
"precreate": "zip -r Lambda-Deployment.zip * -x *.zip *.log node_modules/aws-sdk/*"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^3.2.0",
"aws-sdk": "^2.865.0"
}
}
--publish ⇒ 생성과 동시에 배포
참고 ⇒ PS C:\serverless\get-video-list> aws lambda create-function help

#3 람다 함수 작성
async 모듈의 waterfall() 함수 사용법
// https://caolan.github.io/async/v3/docs.html#waterfall
var async = require("async");
async.waterfall([
function(firstcallbackfunc) {
console.log(`첫번째 함수`);
firstcallbackfunc(null, "Peter", "Sam");
},
function(a1, a2, secondcallbackfunc) {
console.log(`두번째 함수 ${a1}, ${a2}`);
secondcallbackfunc(null, "Serverless");
},
function(a3, thirdcallbackfunc) {
console.log(`세번째 함수 ${a3}`);
thirdcallbackfunc(null, "Done")
}
], function(err, result) {
console.log(`최종 콜백 ${err}, ${result}`);
});
C:\serverless\get-video-list\index.js
// P172
'use strict';
// 필요 모듈 추가 및 S3 객체 생성
var AWS = require('aws-sdk');
var async = require('async');
var s3 = new AWS.S3();
// next : callback 함수
// next(ERROR, DATAS, ...)
// next(null, ...) ==> 오류가 발생하지 않았으며, 어떤 값을 반환
// next(어떤값) ==> callback 함수로 오류를 반환
// S3.listObjects 함수 호출에 사용할 입력 포맷을 생성
function createBucketParams(next) {
var params = {
Bucket: process.env.BUCKET,
EncodingType: 'url'
};
next(null, params); // #1 함수가 호출
}
// #1 버킷의 객체(파일) 목록을 조회
function getVideosFromBucket(params, next) {
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listObjects-property
s3.listObjects(params, function(err, data) {
if (err) {
next(err);
} else {
next(null, data); // #2 함수가 호출
}
});
}
// #2 버킷의 객체 목록 조회 결과를 반환 형식에 맞춰서 변형
function createList(data, next) {
console.log(data);
// 버킷의 객체 이름(폴더명과 확장자를 포함)을 저장할 배열
var urls = [];
for (var i = 0; i < data.Contents.length; i ++) {
var file = data.Contents[i];
// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/substr
// 키(객체 이름)의 마지막 세글자가 mp4인 경우
if (file.Key && file.Key.substr(-3, 3) === 'mp4') {
urls.push(file);
}
}
var result = {
baseUrl: process.env.BASE_URL, // 버킷 접근 URL
bucket: process.env.BUCKET,
urls: urls
};
next(null, result); // #3 함수로 전달
}
exports.handler = function(event, context, callback) {
async.waterfall([
createBucketParams,
getVideosFromBucket,
createList
],
// #3 함수 : [ ... ]에 정의된 함수가 모두 정상 수행 또는 오류가 발생한 경우에 호출
function(err, result) {
if (err) {
callback(err);
} else {
callback(null, result); // 버킷에 저장된 객체 목록을 버킷 접속 주소, 버킷 ARN과 함께 반환
}
});
};
#4 람다 함수 생성 및 배포
PS C:\serverless\get-video-list> npm run create
:
An error occurred (AccessDeniedException) when calling the CreateFunction operation: User: arn:aws:iam::199503606661:user/lambda-upload is not authorized to perform: iam:PassRole on resource: arn:aws:iam::199503606661:role/lambda-s3-execution-role ⇒ iam:PassRole을 추가
npm ERR! code ELIFECYCLE
npm ERR! errno 254
npm ERR! get-video-list@1.0.0 create: `aws lambda create-function --function-name get-video-list --handler index.handler --memory-size 128 --runtime nodejs4.3 --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip`
npm ERR! Exit status 254
npm ERR!
npm ERR! Failed at the get-video-list@1.0.0 create script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\i\AppData\Roaming\npm-cache\_logs\2021-03-17T06_28_26_758Z-debug.log
#5 Lambda-DevOps 그룹에 권한을 추가


#6 다시 람다 함수 생성 및 배포
PS C:\serverless\get-video-list> npm run create
:
An error occurred (InvalidParameterValueException) when calling the CreateFunction operation: The runtime parameter of nodejs4.3 is no longer supported for creating or updating AWS Lambda functions. We recommend you use the new runtime (nodejs12.x) while creating or updating functions. ⇒ 런타임 버전이 낮아서 함수를 생성할 수 없음
npm ERR! code ELIFECYCLE
npm ERR! errno 254
npm ERR! get-video-list@1.0.0 create: `aws lambda create-function --function-name get-video-list --handler index.handler --memory-size 128 --runtime nodejs4.3 --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip`
npm ERR! Exit status 254
npm ERR!
npm ERR! Failed at the get-video-list@1.0.0 create script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\i\AppData\Roaming\npm-cache\_logs\2021-03-17T06_42_58_746Z-debug.log
#7 런타임 버전 변경 후 다시 람다 함수 생성 및 배포
c:\serverless\get-video-list\package.json
{
"name": "get-video-list",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"create": "aws lambda create-function --function-name get-video-list --handler index.handler --memory-size 128 --runtime nodejs12.x --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip",
"precreate": "zip -r Lambda-Deployment.zip * -x *.zip *.log node_modules/aws-sdk/*"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^3.2.0",
"aws-sdk": "^2.865.0"
}
}
PS C:\serverless\get-video-list> npm run create
:
> get-video-list@1.0.0 create C:\serverless\get-video-list
> aws lambda create-function --function-name get-video-list --handler index.handler --memory-size 128 --runtime nodejs12.x --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip
{
"FunctionName": "get-video-list",
"FunctionArn": "arn:aws:lambda:us-east-1:199503606661:function:get-video-list",
"Runtime": "nodejs12.x",
"Handler": "index.handler",
"CodeSize": 557793,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-03-17T06:47:31.507+0000",
"CodeSha256": "NyFR3MY462w8GWw2xysqgP13albNUfi4K+gstWh+rNI=",
"Version": "1",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "1ae981a6-5d03-4ffd-999f-5622b53780e2",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}

#8 함수 실행에 필요한 환경 변수 설정

BUCKET 환경 변수 ⇒ 트랜스 코딩된 결과가 저장된 버킷 이름

BASE_URL 환경 변수 ⇒ 트랜스 코딩된 객체에 객체 URL에서 키를 제외한 부분 ⇒ https://버킷이름.s3.amazonaws.com

#9 람다 함수 테스트
해당 함수는 외부(함수를 호출하는 곳)에서 전달하는 값을 사용하는 부분이 없음 ⇒ event 객체를 사용하는 부분이 없음
⇒ 테스트를 수행할 때 별도의 값 설정이 필요 없음

동영상 파일 목록을 반환하는 것을 확인 (모두 세 개를 반환)

API Gateway에 get-video-list API를 추가 (P193)
#1 get-video-list API 생성



#2 리소스 생성


#3 메소드 생성


#4 메소드 테스트


#5 CORS 활성화 및 API 배포




웹 사이트에 비디오 목록을 가져와서 재생하는 기능을 추가 (P209. 참고로 책 내용과는 상이)
#1 config.js 파일에 get-file-list API 호출 URL을 추가
var configConstants = {
auth0: {
domain: 'naanjini.us.auth0.com',
clientId: 'BAdEeZTkFlAdyC1a7nWENB3sRydpByXR'
},
// user-profile API 호출 URL
apiBaseUrl: 'https://vfhp67njsk.execute-api.us-east-1.amazonaws.com/dev',
// get-file-list API 호출 URL
getFileListApiUrl: 'https://6ln109dh77.execute-api.us-east-1.amazonaws.com/dev'
};

#2 video-controller.js 파일을 추가

c:\serverless\24-hour-video\js\video-controller.js
// P209
var videoController = {
data: {
config: null
},
uiElements: {
videoCardTemplate: null,
videoList: null
},
init: function(config) {
// index.html 문서에 id 속성이 video-template, video-list인 요소를 참조
this.uiElements.videoCardTemplate = $('#video-template');
this.uiElements.videoList = $('#video-list');
// config.js 파일에 있는 내용을 참조
this.data.config = config;
this.getVideoList();
},
// get-video-list API를 호출
getVideoList: function() {
var that = this;
// get-video-list API 호출 URL + 리소스 이름
// videos 리소스를 GET 방식으로 호출 --> get-video-list 람다 함수를 실행하고 결과를 반환 받음
var url = this.data.config.getFileListApiUrl + '/videos';
$.get(url, function(data, status) {
that.updateVideoFrontPage(data);
});
},
// get-video-list 람다 함수의 실행 결과를 목록으로 화면에 출력
updateVideoFrontPage: function(data) {
console.log(data);
// TODO
}
};
#3 main.js 파일에 videoController 객체의 초기화 함수 호출을 추가
c:\serverless\24-hour-video\js\main.js
// 즉시 실행 함수
(function() {
console.log("### 1");
// 해당 웹 페이지 문서가 로딩되면 설정 정보를 가져와서 설정
$(document).ready(function() {
console.log("### 2");
// user-controller.js에 선언되어 있는 userController 객체의 init 메소드를 호출
// coonfig.js에 선언되어 있는 configConstants 객체를 인자로 전달
userController.init(configConstants);
videoController.init(configConstants); /* 추가 */
});
})();
#4 index.html 파일에 동영상 목록과 출력 부분을 추가
c:\serverless\24-hour-video\index.html
<!-- (생략) -->
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<!-- 대략 66번 라인
<div class="container">
<h1>Hello, world!</h1>
<p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p>
</div>
-->
<!-- 추가 시작: P209 코드 참조 -->
<div class="container" id="video-list-container">
<div id="video-template" class="col-md-6 col">
<div class="video-card">
<!-- 동영상을 재생해 주는 요소(태그) -->
<video width="100%" height="100%" controls>
<!-- video 태그에 사용될 동영상의 소스 -->
<!-- src 속성에 동영상의 주소를 추가하면 재생이 가능 -->
<source type="video/mp4">
지원하지 않는 타입
</video>
</div>
</div>
<div calss="row">
<!-- S3 버킷에 저장된 동영상의 목록을 제시하고
동영상 이름을 클릭하면 video 태그를 통해서 동영상 재생 -->
<ul id="video-list">
</ul>
</div>
</div>
<!-- 추가 끝 -->
</div>
<!-- (생략) -->
<script src="https://cdn.auth0.com/js/lock/11.27/lock.min.js"></script>
<script src="js/config.js"></script>
<script src="js/user-controller.js"></script>
<script src="js/video-controller.js"></script> <!-- 추가 -->
<script src="js/main.js"></script>
<!-- (생략) -->
#5 웹 페이지 테스트

#6 S3 버킷 목록을 화면에 출력
c:\serverless\24-hour-video\js\video-controller.js
updateVideoFrontPage: function(data) {
console.log(data);
// <ul id="video-list">
//
동영상 파일명
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~
// baseUrl urls.Key urls.Key에서 파일명만 추출
var baseUrl = data.baseUrl;
var urls = data.urls;
/*
for (var i = 0; i < urls.length; i ++) {
var url = urls[i];
var key = url.Key;
var filename = key.split('/')[1];
var litag = '<li url="' + baseUrl + '/' + key + '">' + filename + '</li>';
$('#video-list').append(litag);
}
*/
urls.forEach(url => {
var key = url.Key;
var filename = key.split('/')[1];
var litag = `<li url="${baseUrl}/${key}">${filename}</li>`;
$('#video-list').append(litag);
});
}

#7 목록을 클릭했을 때 비디오가 재생
c:\serverless\24-hour-video\js\video-controller.js
// P209
var videoController = {
data: {
config: null
},
uiElements: {
videoCardTemplate: null,
videoList: null
},
init: function(config) {
// index.html 문서에 id 속성이 video-template, video-list인 요소를 참조
this.uiElements.videoCardTemplate = $('#video-template');
this.uiElements.videoList = $('#video-list');
// config.js 파일에 있는 내용을 참조
this.data.config = config;
this.getVideoList();
this.wireEvents(); // 이벤트 핸들러를 추가
},
// get-video-list API를 호출
getVideoList: function() {
var that = this;
// get-video-list API 호출 URL + 리소스 이름
// videos 리소스를 GET 방식으로 호출 --> get-video-list 람다 함수를 실행하고 결과를 반환 받음
var url = this.data.config.getFileListApiUrl + '/videos';
$.get(url, function(data, status) {
that.updateVideoFrontPage(data);
});
},
// get-video-list 람다 함수의 실행 결과를 목록으로 화면에 출력
updateVideoFrontPage: function(data) {
console.log(data);
// <ul id="video-list">
//
동영상 파일명
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~
// baseUrl urls.Key urls.Key에서 파일명만 추출
var baseUrl = data.baseUrl;
var urls = data.urls;
/*
for (var i = 0; i < urls.length; i ++) {
var url = urls[i];
var key = url.Key;
var filename = key.split('/')[1];
var litag = '<li url="' + baseUrl + '/' + key + '">' + filename + '</li>';
$('#video-list').append(litag);
}
*/
urls.forEach(url => {
var key = url.Key;
var filename = key.split('/')[1];
var litag = `<li url="${baseUrl}/${key}">${filename}</li>`;
$('#video-list').append(litag);
});
},
// 추가된 부분 시작
// 이벤트를 처리하는 함수(이벤트 핸들러)를 정의
// https://developer.mozilla.org/ko/docs/Web/HTML/Element/Video
wireEvents: function() {
// id 속성(attibute) 값이 video-list인 요소(element, tag) 아래에서
// li 요소에 click 이벤트가 발생했을 때 수행할 동작을 정의
$('#video-list').on('click', 'li', function() {
// 클릭한 li 태그(요소)의 url 속성의 값을 가져와서 url 변수에 할당
var url = $(this).attr('url');
/*
<video width="100%" height="100%" controls>
<!-- video 태그에 사용될 동영상의 소스 -->
<!-- src 속성에 동영상의 주소를 추가하면 재생이 가능 -->
<source type="video/mp4">
지원하지 않는 타입
</video>
*/
// source 태그(요소)의 src 속성의 값으로 url 변수의 값을 설정
$('source').attr('src', url);
// video 태그(요소)에 설정된 동영상 파일을 읽어들림
$('video').load();
});
}
// 추가된 부분 끝
};
'CLOUD > AWS' 카테고리의 다른 글
3/19 - AWS 마무리 정리 (0) | 2021.03.19 |
---|---|
3/19 - AWS 15차시 (0) | 2021.03.19 |
3/16 - AWS 13차시 (0) | 2021.03.16 |
3/15 - AWS 12차시 (0) | 2021.03.15 |
3/12 - AWS 11차시 (0) | 2021.03.12 |