지난 포스팅에 이어 로그인을 구현해보았다.
이 로그인 구현에 정말 시간을 많이 투자했다.
우선은 로그인 페이지 구현을 위해 프로젝트 구조를 수정했다.
현재는 이렇게 외부에서 요청이 들어오면
NGINX가 정적 html파일과 css. js 파일을 반환하면서 프론트를 서비스한다.
그리고 프론트에 뿌려줄 데이터는 다른 오리진으로 분리된 백엔드 서버에서 받아온다.
원래의 내 목표는 로그인에 성공하면 세션을 주어 로그인을 유지하는 기능을 구현하는 것이었다.
하지만 이렇게 구현하였더니 프론트가 유저 정보를 백엔드로 보내 로그인에 성공해도
세션이 백엔드 서버에 대해 생기는 문제가 발생했다.
everdu.ga 라는 프론트 도메인이 있고
api.everdu.ga 라는 백엔드 도메인이 있을 때
로그인 처리를 백엔드에서 하면 api.everdu.ga 로 접속해야 세션 아이디 쿠키를 보낸다.
everdu.ga 로 접속하면 서버로 쿠키를 보내지 않는다.
이 문제를 해결하기 위해 2가지 아이디어를 떠올렸다.
첫번째는 프론트와 백의 오리진을 일치시켜 CORS를 피하는 것
두번째는 그냥 프론트랑 백을 하나의 어플리케이션으로 합치는 것이다.
찾아보니 서버간의 통신에는 CORS가 문제되지 않아서 프론트 서버를 따로두는 것도 방법이라고 한다.
이것도 또 다른 방법이겠지만 지금은 서버 2개만으로도 충분히 벅차서 3개를 다루는 건 패스했다..
그리고 고민 끝에 첫번째 방법으로 해보기로 했다.
일단 이제와서 앱 하나로 합치는 건 귀찮을 것 같기도 하고, ejs 같은 기능을 쓰는 건 아직은 하고 싶지 않았다.
지금은 순수 자바스크립트로 구현하고 싶은 욕심이 가득하다.
그래서 everdu.ga 는 프론트로
everdu.ga/api 는 백엔드로 가도록 프록싱 해주었다.
api도 웹 앱마다 다른 프로그램으로 가야하니까 유튜브 클론코딩 api 에 대해 따로 설정해주었다.
하지만 이렇게만 하면 노드 앱에서는 매번 /api/project/ym_clone 라는 주소가 붙은채로 요청이 와서 라우팅을 하기가 불편하다.
그래서 Router 객체를 따로 빼서 /api/project/ym_clone 라는 주소에 대해 라우팅 하도록 설정했다.
...
app.use('/api/project/ym_clone/', router);
...
이제 백엔드에 데이터를 요청할 때는 everdu.ga/api/project/ym_clone/ 주소로 요청을 보내면 된다.
오리진은 동일하기 때문에 CORS 설정 없이 데이터를 주고 받을 수 있고,
세션이나 쿠키도 백엔드에서 설정하면 프론트로 요청을 보낼 때도 쿠키를 담게 된다.
다음으로 로그인 페이지를 만들었다.
로그인 페이지는 간단하게 만들었다.
기본 input UI에 둥근 테두리만 넣어줬는데도 깔끔해보여서 마음에 든다.
그래도 분명 디자인적인 개선점은 남아있겠지만ㅋㅋ
폼 태그를 쓰긴 했는데, 폼 액션을 사용하면 백엔드 페이지로 이동하는게 불편해서
event.preventDefault() 메소드로 submit 기본 기능을 막았다.
우선 로그인 버튼을 누르면 폼 유효성 검사를 하도록 했다.
// validating form
const id_reg = /^[0-9a-zA-Z]([-_]?[0-9a-zA-Z]){4,15}$/;
if (!id_reg.test(id)) {
alert("wrong id!");
return false;
}
정규표현식을 내가 직접 써본 건 처음인데 아직은 어려운 것 같다.
1. 처음엔 숫자나 알파벳이 와야하고
2. 중간에 -_ 가 하나 있거나 없어도 되고
3. 이후에 숫자나 알파벳이 오는 것을
4. 최소 4번에서 15까지 반복한다.
이런 의도로 작성했다.
다음으로 유효성검사를 통과하면 fetch API 로 서버에 아이디 패스워드를 보낸다.
const API_ADDRESS = '로그인을 처리할 주소';
fetch(API_ADDRESS, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then((response) => {
return response.json();
}).then((data) => {
if (data.success) {
window.location.href = '../';
}
else {
alert(data.msg);
}
}).catch((err) => {
console.log(err);
});
인증이 성공했는지 실패했는지만을 받아서 성공했으면 메인화면으로 이동하고
실패했으면 어떤 이유로 실패했는지를 담은 msg 를 보여주도록 했다.
이제 프론트에서 받은 데이터를 처리하는 백엔드 로직을 작성해준다.
백엔드는 node 로그인 구현을 공부하면서 배운 mvc 패턴으로 구성했다.
로그인 라우팅에 대한 컨트롤러 함수를 다음과 같이 구현했다.
login: function(req, res) {
const result_data = {};
const query = 'select id, password' +
' from user' +
" where id = '" + req.body.id + "'";
db.query(query, (err, result) => {
if (!!result) {
if (result.length === 0) {
console.log("해당하는 아이디가 없습니다.");
result_data.success = false;
result_data.msg = "no_id";
}
else {
if (req.body.pw === result[0].password) {
console.log("login");
result_data.success = true;
// cookie test
res.cookie("auth", "true", {
maxAge: 1000*60*1,
httpOnly: true,
});
}
else {
console.log("wrong password");
result_data.success = false;
result_data.msg = "wrong_pw";
}
}
res.send(result_data);
}
else {
console.log(err);
}
});
}
로그인에 성공하면 auth 라는 키를 가진 쿠키를 생성하도록 했다.
그래서 앞으로 auth 라는 키의 값이 true 인 쿠키를 보내는 경우에는 인증된 것으로 보고 로그인을 요구하지 않는다.
이제 로그인 여부에 따라서 화면을 다르게 보여주는 작업의 기반을 위해
로그인 여부를 확인하는 코드를 백과 프론트에 작성해주었다.
백에서는 프론트에 보여줄 데이터를 가져오기 전에 auth 쿠키가 있는지 확인하고
해당 쿠키가 없으면 need login 이라는 값을 보낸다.
프론트에 뿌려줄 데이터를 받아오는 코드에서는 need login 이라는 값이 있으면
로그인이 필요하다는 메세지를 띄우고, 빈화면을 보여준다.
// 백엔드
...
const resultObj = {
status: "need login",
msg: "not login",
};
const cookies = req.cookies;
if (cookies.auth)
{
resultObj.status = "success";
....
}
.....
// 프론트엔드
...
.then((data) => {
if (data.status === "need login")
{
alert("need login");
return;
}
....
}
다음은 결과 사진이다.
이제는 노래를 클릭하면 음악이 재생되는 것
그리고 가수 페이지를 만들어볼 생각이다
이를 위해 이제는 DB 구조를 제대로 고민할 필요가 있을 것 같다.
'개인 프로젝트 > [2022] 유튜브 뮤직 클론코딩' 카테고리의 다른 글
[유튜브 뮤직 클론코딩] 8. forever를 이용한 배포 자동화 & 플레이어 추가 (0) | 2022.08.19 |
---|---|
[유튜브 뮤직 클론코딩] 7. 로그인 시 발생할 수 있는 에러처리 (0) | 2022.08.16 |
[유튜브 뮤직 클론코딩] 5. MariaDB + NodeJS 연동하기 (0) | 2022.06.08 |
[유튜브 뮤직 클론코딩] 4. Express 프레임워크 적용과 API 서버에서 데이터 받기 (0) | 2022.06.05 |
[유튜브 뮤직 클론코딩] 3. Fetch API & Node.js 에서 CORS 해결하기 (0) | 2022.05.20 |