[Node.js] 생활코딩 / 섹션 4. APP + 섹션 7. 보안
목차
글 생성 UI 만들기
data에 있는 파일 이름을 읽어 (fs.readdir) 생성하며, 링크 또한 해당 파일로 접근할 수 있도록 걸어준다 ! 이 때 새로운 라우팅 경로를 생성하는 것이 아닌, 기존 경로를 재활용하여 id 값만 파일의 이름으로 바꾸어준다.
내용을 작성할 수 있도록 만들어둔 폼이 작동할 때 create_process 링크로 연결되도록 하며, post 메소드를 사용하여 정보를 전송한다.
* post 메소드는 다른 메소드에 비해 처리 속도는 느리지만, url에 해당 내용이 표시되지 않아 보안성이 강하다
else if(pathname ==='/create'){
fs.readdir('./data', function(error, filelist){
var title = 'Web - create'; // 제목 지정
// 목차 리스트 생성 -> list 변수에 저장
var list = '<ul>';
var i = 0;
while(i<filelist.length){
list += `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
i += 1
}
list = list+'</ul>'
// 제목, 내용 제출 가능한 폼
// 제출 시 /process_create 로 이동
var template = templateHTML(title, list, `
<form action = "/create_process" method="post">
<p><input type="text" name="title" placeholder="title"></p>
<p><textarea name="description" placeholder="description"></textarea></p>
<p><input type = "submit"></p>
</form>
`);
response.writeHead(200);
response.end(template);
});
}
POST 방식으로 전송된 데이터 받기 + 파일 생성과 리다이랙션
이 때 만들어둔 칸에 작성한 데이터를 받아오려면 아래 코드가 필요하다.
받아온 데이터를 post 변수에 저장 후 제목과 내용을 분리하여 저장한다. fs.writeFile 함수를 활용하여 data 디렉토리에 제목의 이름으로 된 파일을 생성한다.
else if(pathname === '/create_process'){ // submit 시 이동되는 주소
var body = '';
request.on('data', function(data){
body += data;
}); // 받아온 데이터를 body에 저장
request.on('end', function(){
var post = qs.parse(body); // body 의 내용 파싱하여 저장
var title = post.title;
var description = post.description;
// data/title의 주소에 description을 내용으로 하는 파일 생성
// 302 : redirection
fs.writeFile(`data/${title}`, description,'utf8', function(err){
response.writeHead(302, {Location: `/?id=${title}`});
response.end();
});
});
}
글 작성
해당 글의 페이지로 redirect
생성된 파일
수정 링크 생성 및 수정 정보 전송
수정 버튼 생성
// HTML을 수정하는 경우 링크 = /update?id=HTML
<a href="/update?id=${queryData.id}">update</a>
/update 로 요청이 들어오면 해당 파일을 읽어 그 내용을 표시한 폼(기존 폼에 value를 사용하여 내용 표시)을 생성한다
else if(pathname ==='/update'){
fs.readdir('./data', function(error, filelist){
fs.readFile(`data/${queryData.id}`, 'utf-8', function(err, description){
var title = queryData.id;
var list = '<ul>';
var i = 0;
while(i<filelist.length){
list += `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
i += 1
}
list = list+'</ul>'
// 기존 폼과 같으나, 원래 글의 내용을 표시하기 위해 value 사용
var template = templateHTML(title, list, `
<form action = "/update_process" method="post">
<input type = "hidden" name="id" value="${title}">
<p><input type="text" name="title" placeholder="title" value="${title}"></p>
<p><textarea name="description" placeholder="description" value="${description}"></textarea></p>
<p><input type = "submit"></p>
</form>
`);
response.writeHead(200);
response.end(template);
});
});
}
파일명 변경 및 내용 저장
제목이 수정될 가능성을 염두에 두어, 기존 제목인 post.id를 id 변수에 저장해두고, 이를 수정해야할 파일의 이름으로 후에 전달한다.
fs.rename 함수를 사용하여 id 이름의 파일을 새로 작성한 title 로 이름을 바꾸고, 수정된 내용 (description)을 그 내용으로 저장한다.
else if(pathname === '/update_process'){
var body = '';
request.on('data', function(data){
body += data;
});
request.on('end', function(){
var post = qs.parse(body);
var id = post.id // 어떤 글이 수정 중인 지 알기 위해 기존 제목을 id로 받아옴
var title = post.title;
var description = post.description;
fs.rename(`data/${id}`, `data/${title}`, function(error){
fs.writeFile(`data/${title}`, description,'utf8', function(err){
response.writeHead(302, {Location: `/?id=${title}`});
response.end();
});
});
});
}
글 삭제 기능 완성
// link 만으로 접속하여 삭제하는 일을 방지해야하기에, form과 post 메소드 사용
<form action="delete_process" method="post">
<input type="hidden" name="id" value="${queryData.id}">
<input type="submit" value="delete">
</form>
fs.unlink 함수를 사용하여 파일 삭제
else if(pathname === '/delete_process'){
var body = '';
request.on('data', function(data){
body += data;
});
request.on('end', function(){
var post = qs.parse(body);
var id = post.id
fs.unlink(`data/${id}`, function(error){ // data/${id} 삭제
response.writeHead(302, {Location: `/`});
response.end();
})
});
}
보안
입력 정보 보안
readFile 을 사용할 경우, 후에 DB를 사용하는 상황을 가정하였을 때 queryData의 조작만으로 주요 정보에 접근할 수 있다는 보안상 취약점이 발생한다. 이를 방지하기 위해 parse 를 사용할 수 있다.
기본적인 path.parse의 경우, 경로의 객체가 반환되며, 그 중 base에는 최종 파일의 이름이 존재한다. 이 때, .base를 사용하면 그 앞의 디렉토리 정보를 세탁하고, 파일의 이름만 남겨 경로를 지정할 수 있기에 보안을 강화할 수 있다.
var filteredID = path.parse(queryData.id).base;
// 후에 모든 queryData.id를 참조하는 부분을 filteredID로 변경한다
출력 정보 보안
동적 파일(ex. php, js …)이 업로드될 시, 이를 출력하는 과정에서 개발자가 의도하지 않은 동작이 일어날 수 있다.
script 태그로 묶인 js 코드의 경우, 껵쇠로 묶인 문법을 해석하지 않고 < > 와 같은 엔티티를 사용하여 그대로 브라우저에 출력하도록 하면 동적인 문법들이 해석되지 않도록 할 수 있다.
sanitize-html
package.json 파일이 생성
npm init
...
Is it ok? (yes) -> enter
의존성 파일들을 담은 node_modules 폴더 생성
npm install -S sanitize-html
script 태그와 같이 위험한 태그를 다음과 같은 방법을 통해 제할 수 있다
var sanitizeHtml = require('sanitize-html')
var dirty = 'some really tacky HTML';
var clean = sanitizeHtml(dirty); // sanitize 할 변수를 인자로 넣어준다
* allowedTags : [’’] 를 사용하여 허용할 태그를 설정할 수 있음