1차 공부/WIL

항해 1주차 풀스택프로젝트 WIL

공대탈출 2022. 11. 20. 22:31

일단 너무 어려웠다. 웹개발 종합반강의로 들었을때는 '아 ajax가 이거구나', '아 style은 이렇게 설정하는구나',

'아 부트스트랩에서 제공하는 코드는 정말 완벽하구나', '뭐야 백엔드 쉽네?' 싶었다.

그런데 풀스택 프로젝트를 하며 정말 나는 강의를 몇번이나 들었음에도, 쉬운것만 배웠던 것임을 알게 되었다.

 

처음으로 github을 쓰는것 부터 어려웠다.

시작하고 팀장님이 git repo를 만들고 그걸 pull하고... git의 기능을 아직도 완벽히 알았다 하긴 뭐하지만

그래도 프로젝트를 진행하며 pull과 push에 대해 정확히 알게 되었다.

merge를 해야한다면 아직도 정신이 아찔해지겠지만, 다행히 프로젝트를 진행하며 내가 pull하지않고 push를 진행해 merge를 해야하는 일은 없었다.

 

일단 JWT와 API, 서버사이드렌더링의 장점에 대해 알게 된 것을 작성 한 후 내가 작성했던 코드들을 살펴보고자 한다.


JWT란 무엇인가?

JWT란 JSON WEB TOKEN의 줄임말로, 인증에 필요한 정보들을 암호화시킨 JSON토큰이다.

사실 말도 그렇고, 방식도 아직 너무 어렵다.

  JWT인증방식
1 사용자가 ID와 password를 서버에 보내 로그인 인증을 요청합니다. 
2 서버에서 인증요청을 받으면 
(Header)JWT에서 사용할 타입과 해시알고리즘 종류, 
(Payload)서버에서 첨부된 사용자 권한 정보와 데이터를 
(Signature)Base 64 URL-safe Encode 한 후 Header에 명시된 해시함수를 적용한다.
3 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장한다.
4 요청시 Header에 Access Token을 담아서 보낸다.
5 서버에서 JWT가 자신이 발행한 토큰인지 인증하고 일치하면 통과, 불일치하면 통과되지 않는다.
6 통과되면 페이로드에 들어있는 유저들의 정보들을 골라서 클라이언트에게 돌려준다.
7 클라이언트가 서버에 요청을 했는데 기간만료됐을 경우 리프레시 토큰을 이용해서 새로운 Access Token을 받는다.

 

JWT의 장점은 무엇일까?

기존 세션 / 쿠키 저장대비 장점으로는 크게 두가지가 있다.

1 별도의 저장소를 관리해야하는 세션, 쿠키에 비해 JWT는 발급한 후 검증만 하기 때문에 추가적인 저장소가 필요하지 않아 서버를 확장하거나 유지, 보수하는데 유리하다.
2 페이스북, 구글, 마이크로소프트 등의 로그인은 토큰 기반으로 인증을 하는데, 권한을 받을수도 프로필을 써드파티 웹사이트에 제공하도록 허가 할 수도 있다.(확장성이 우수함)

서버사이드 렌더링의 장점

  서버사이드 렌더링의 장점
1 검색 사이트에서 검색했을 때 결과가 사용자에게 많이 노출될 수 있도록 최적화 하는 기법.(검색엔진 최적화)
2 서버에서 페이지를 그려 클라이언트로 보내 화면에 표시하기때문에 페이지 렌더링이 빠르다.
 (첫 페이지에 해당하는 문서만 클라이언트에 전달해 렌더링 하기 때문에 초기 로딩속도가 클라이언트 사이드 렌더링보다 빠릅니다.)(빠른 페이지 렌더링)

API

이번 풀스택 프로젝트때 처음 작성했던 API설계이다.

사실 저거 팀장님이 거의 다 작성하셨는데, 그땐 도대체 뭘 적는건지 왜 저게 POST인지 GET인지 이해가 안갔는데

지금 보니 어느정도 흐릿하게나마 이해가 가는것 같다.

 

API란 뭘까?

웹 개발에서의 API란 개발자가 앱을 통해 사용자의 웹 브라우저에서 상호작용할 수 있도록 하는 코드 기능들, 사용자의 컴퓨터상에 있는 다른 소프트웨어 및 하드웨어, 또는 서드파티 웹사이트나 서비스의 집합을 의미한다.


작성한 코드들

 

html - 함수관련

    <script>
        function log_In() {
            // 인풋값을 저장
            let loginid = $('#id-input').val()
            let loginpassword = $('#password-input').val()
            //아이디 비번 서버에 전달
            $.ajax({
                type: 'POST',
                url: '링크/api/login',
                data: {
                    id: loginid,
                    password: loginpassword
                },// loginSuccess값과 토큰을 가져옴
                success: function (response) {
                    if (response['loginSuccess'] === true) {
                        $.cookie('x_auth', response['token']);
                        alert('로그인이 완료되었습니다.')
                        if (document.cookie !== "") {
                            $('#loginbtn').hide()
                            $('#regBtn').hide()
                            $('#logoutbtn').show()
                            $('#reviewbtn').show()
                            $('#modalres').hide()
                            localStorage.setItem('loginId', loginid)

                            //리뷰 쪽 작성자 불러오기
                            $.ajax({
                                type: 'GET',
                                url: '링크/api/writer',
                                data: {},
                                success: function (response) {

                                    console.log(response)

                                    for (let i = 0; i < response.length; i++) {
                                        let writer = response[i]['name']
                                        let id = response[i]['id']
                                        console.log(writer, id)

                                        if (id === localStorage.getItem('loginId')) {
                                            localStorage.setItem('writer', writer)
                                        }
                                    }
                                }
                            })

                        }
                    } else if (response['loginSuccess'] === false) {
                        alert(response['loginmsg'])
                    } else if (response['passwordSuccess'] === false) {
                        alert(response['passmsg'])
                        $('#password-input').val('')
                    }
                }
            });
        }
        function log_out() {
            $.ajax({
                type: 'GET',
                url: '링크/api/logout',
                data: {
                },
                success: function (response) {
                    //로그아웃시 쿠키삭제
                    $.removeCookie('x_auth', response['token']);
                    alert('로그아웃이 완료되었습니다.')
                    window.location.reload()
                    //로그아웃버튼 숨기고 로그인버튼 출력
                    $('#login').show()
                    $('#regBtn').show()
                    $('#logout').hide()
                    $('#reviewbtn').hide()
                    localStorage.clear()
                }
            })
        }
        function logcancle() {
            window.location.reload()
        }
    </script>

 

html

<div class="loginModalbox">
        <div id="modalres" id="loginModal" class="modal">
            <div class="loginmodal_body">
                <div class="loginTitle">
                    <br />
                    <h6>세상에 나쁜 게임은 없다! </h6>
                    <h1>로그인</h1>
                    <br /><br /><br /><br />
                    <h6>세나게에 오신 것을 환영합니다</h6>
                </div>
                <br />
                <br />
                <div class="mb-3">
                    <label for="formGroupExampleInput" class="form-label">ID (。・∀・)ノ</label>
                    <br />
                    <input id="id-input" type="text" class="form-control" id="formGroupExampleInput"
                        placeholder="아이디를 입력하여 주세요.">
                </div>
                <br />
                <div class="mb-3">
                    <label class="form-label">Password (((o(*゚▽゚*)o)))</label>
                    <br />
                    <input id="password-input" type="password" class="form-control" id="formGroupExampleInput2"
                        placeholder="비밀번호를 입력하여주세요.">
                </div>
                <br />
                <div style="text-align: center;" class="loginButton">
                    <button style="font-family:'NeoDunggeunmo'; display :inline-block;" id="login" onclick="log_In()"
                        type="button" class="btn btn-outline-success"> 로그인하기 </button>
                    <button style="font-family:'NeoDunggeunmo'; display :inline-block;" id="loginClose"
                        onclick="logcancle()" type="button" class="btn btn-outline-secondary"
                        data-bs-dismiss="loginModal"> 그만두기 </button>
                </div>
            </div>
        </div>

    </div>
    
 <div class="logbtn">

            <button style="font-family:'NeoDunggeunmo';" id="loginbtn" class="login-btn-open-popup">로그인</button>
            <button style="font-family:'NeoDunggeunmo';" onclick="log_out()" class="logoutbtn" id="logoutbtn"> 로그아웃
            </button>
            
        </div>

 

모달 버튼 스크립트

<script>
    //
    const loginModalbox = document.querySelector('.loginModalbox');
    const loginmodal = document.querySelector('#modalres');
    const loginbtnOpenPopup = document.querySelector('.login-btn-open-popup');
    const loginClose = document.querySelector('#loginClose')
    // 
    loginbtnOpenPopup.addEventListener('click', () => {
        loginmodal.classList.toggle('show');

        if (loginmodal.classList.contains('show')) {
            loginModalbox.style.overflow = 'hidden';
        }
    });
    //
    loginmodal.addEventListener('click', (event) => {
        if (event.target === loginmodal) {
            loginmodal.classList.toggle('show');

            if (!loginmodal.classList.contains('show')) {
                loginModalbox.style.overflow = 'auto';
            }
        }
    });
    //
    loginClose.addEventListener("click", (event) => {
        if (event.target === loginClose) {
            loginmodal.classList.toggle('show');
            if (!loginmodal.classList.contains('show')) {
                loginModalbox.style.overflow = 'auto';
            }
        }
    });
</script>

 

서버단

router.post('/login', (req, res) => {
  //============================================  
  //요청된 이메일을 데이터베이스에서 있는지 찾는다 =
  //============================================
    User.findOne({id: req.body.id}, (err, user) => {
      if(!user) {
        return res.json({
          loginSuccess: false,
          loginmsg: "제공된 아이디에 해당하는 유저가 없습니다."
        })
      }
    //요청된 이메일이 데이터베이스에 있다면 비밀번호가 맞는 비밀번호인지 확인.
      user.comparePassword(req.body.password, (err, isMatch) => {
        if(!isMatch)
          return res.json({
            passwordSuccess: false,
            passmsg: "비밀번호가 틀렸습니다."})
          //비밀번호까지 맞다면 토큰을 생성하기.
          user.generateToken((err, user) => {
            if(err) return res.status(400).send(err); 
            // 토큰을 저장한다. 어디에? 쿠키, 로컬스토리지 ... 여기선 쿠키
            res.cookie("x_auth", user.token)
            .status(200)
            //response값을 아래 json객체로 정렬해 보내준다.
            .json({loginSuccess: true, userId: user._id, token: user.token})
        })
      })
    })
  })


  router.get('/users/auth', auth, (req, res) => {
    //여기까지 미들웨어를 통과해 왔다는 얘기는 authentication이 true라는 말
    res.status(200).json({
      _id: req.user._id,
      isAuth: true,
      id: req.user.id,
      name: req.user.name,
    })
  })

  router.get("/logout", auth , (req, res) => {

    User.findOneAndUpdate({ _id: req.user._id }, { token: ""}, (err, doc) => {
        if (err) return res.json({ success: false, err });
        return res.status(200).send({
            success: true
        });
    });
});

너무 길지만 대충 요약하자면, 사이트의 특정 input칸에 아이디와 비밀번호를 입력한다.

입력한 아이디를 MongoDB에서 찾아 일치하는 값이 있다면 비밀번호를 데이터베이스에서 비교한다.

비밀번호가 일치한다면 토큰을 생성해 데이터베이스에 저장하고, 토큰을 쿠키에 담아 웹사이트에 저장한다.

 

토큰이 있다면 로그인, 회원가입 버튼들을 없앤다.

로그아웃 버튼을 누르게되면 DB에 있는 일치하는 _id값의 token을 제거하고, 쿠키에서도 토큰을 제거해준다.

 

간단해 보일수도 있지만, 아직도 '너 썼던 코드 다시 작성해봐' 하면 못할것 같다 ㅋㅋㅋㅋ

 

이번 프로젝트를 하면서 여러가지 문제들이 있었지만, 그 중 하나를 보이자면...

로그인 후 쿠키값에 토큰이 저장되있음에도, 새로고침을 하면 로그아웃버튼이 사라지고 다시 로그인이 안된 상태처럼

사이트가 보여진다는 것이었다.

        .logoutbtn {
            display: none;
        }


<script>
    $(document).ready(function () {
        if (document.cookie !== "") {
            $('#loginbtn').hide()
            $('#regBtn').hide()
            $('#logoutbtn').show()
            $('#reviewbtn').show()

        }
    })
</script>

간단하다고 생각할 수도 있지만 생각보다 그런 콘솔창에 찍히지 않는 에러가 발생하다보니

쿠키값이 사라진건가? DB에서 받아오는데 오류가 있는건가? 하는 쓸데없는 생각들이 들었다.

 

결론은 매우 간단했다.

로그인 버튼은 사이트 진입 시 보여지는 상태로 있고, 로그아웃 버튼은 display가 none으로 디폴트 값이 잡혀있어,

새로고침시 쿠키의 존재여부와 무관하게 사라지고 생기는 것이었다.

 

그래서 구글링을 하여 사이트 진입시 작동하는 함수를 만들었고,

조건문을 사용하여 사이트에 쿠키값이 존재할때 로그인버튼, 회원가입 버튼을 숨기고,

로그아웃, 리뷰남기기 버튼을 보이게 하였다.

 

누군가 보면 저런걸 trouble shooting으로 쓰나? 싶겠지만... 처음 겪는 일이다보니 기억에 남아 쓰게 되었다.

 

나중에 보면 아무것도 아니겠지만, 너무너무 힘들었고, 도움되는 일 없이 폐만 끼친 것 같아 팀원분들에게 매우 죄송했다.

'1차 공부 > WIL' 카테고리의 다른 글

항해 6주차 미니프로젝트  (0) 2022.12.26
항해 5주차 주특기 심화 WIL  (0) 2022.12.18
항해 4주차 주특기 숙련 WIL  (1) 2022.12.13
항해 3주차 주특기 입문 WIL  (0) 2022.12.05
항해 2주차 알고리즘 WIL  (0) 2022.11.27