dukDukz

21.05.28 Token 을 이용한 Login 처리 본문

웹 개발/Node JS

21.05.28 Token 을 이용한 Login 처리

헤일리_HJ 2021. 5. 28. 17:20

전체 구조

메인페이지에서 로그인 시도

로그인 완료 되면 토큰 생성 - user info 에서 id 확인가능

로그인 실패하면 재시도 요청

로그인 시간 만료되면 쿠키 없어짐 - 로그인 만료처리됨

 

폴더 트리

auth.js : 로그인 검증할때 토큰이 유효한지 인증하는 파트이다.

 

 

 

 

main.js : html의 동작을 제어한다

 

.env :  두번째 인자값을 숨긴다.

jwt.js :  token 을 생성해주는 역할

 

 

 

 

 

 

1. html 과 css 먼저 꾸며주자

더보기

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!--css and js-->
    <script src="./js/main.js"></script>
    <link rel="stylesheet" href="./css/main.css">
</head>
<body>
    <button id="loginBtn">로그인</button>
    <a href="/user/info">회원정보 보기</a>
    <div class="layerPopup">    <!--전체를 검정색으로 깔아주는 역할-->
        <div class="loginPopup">    <!--로그인 영역(배경 흰색) 가운데로 설정-->
            <div id="loginForm">
                <h2>로그인페이지</h2>
                <input type="text" name="userid" id="userid" placeholder="아이디 입력하기">
                <input type="password" name="userpw" id="userpw" placeholder="패스워드 입력하기">
                <button id="localLogin" class="loginSubmit">로그인</button>
                <button id="kakaoLogin" class="loginSubmit kakao">카카오로그인</button>
                <p class="joinWord">아직도 회원이 아니세요?
                    <span class="joinBtn">회원가입</span>
                </p>
            </div>
        </div>
    </div>

</body>
</html>

main.css

*{
    margin: 0;
    padding: 0;
}

.layerPopup{
    display: none;
    position: fixed;
    top: 0;
    width: 100%;
    height: 100%;
    background: #000000a6;
}

.layerPopup.open{
    display: block;
}

.loginPopup{
    width: 300px;
    height: 300px;
    box-sizing: border-box;
    padding: 0 50px 50px 50px;
    background: #fff;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}

#loginForm>h2{
    margin-top: 20px;
}

#loginForm>input{
    width: 100%;
    box-sizing: border-box;
    padding: 7px 14px;
    margin-top: 10px;
    border: 1px solid #666;
}

.loginSubmit
{
    width: 100%;
    height: 30px;
    border: 1px solid #666;
    background: #fff;
    margin-top: 10px;
    cursor: pointer;
    border-radius: 15px;
}

#loginForm > .kakao{
    background: yellow;
}

.joinWord{
    margin-top: 10px;
    text-align: left;
    font-size: 12px;
}

.joinWord> .joinBtn{
    float: right;
    color: blue;
    cursor: pointer;
}

 

 

2. main.js

 

기능:

① 로그인 버튼 누르면 로그인창 켜지게

② 배경 누르면 로그인창 꺼지게

③ 로그인 기능 - id와 pw value 값 받아서 POST로 auth/local/login 여기로 요청한다. (fetch 사용)

④ 로그인 성공시 로그인 창 꺼지게함

⑤ 로그인 실패시 입력창 비우고 포커스 이동

더보기
document.addEventListener('DOMContentLoaded',init);
function init(){
    const loginBtn = document.querySelector('#loginBtn');
    const layerPopup = document.querySelector('.layerPopup');
    const localLogin = document.querySelector('#localLogin');
    loginBtn.addEventListener('click',loginBtnFn); 
    layerPopup.addEventListener('click',popupClose);
    localLogin.addEventListener('click',login);
}
// 로그인 버튼 클릭시 열리게
function loginBtnFn(){
    const layerPopup = document.querySelector('.layerPopup');
    layerPopup.classList.add('open');
}
// 배경을 클릭 시 로그인창 꺼지게
function popupClose(event){
    if(event.target == this){  // 배경클릭시에만 open 클래스 지우게 
        this.classList.remove('open')
    }
}

async function login(){
    const userid = document.querySelector('#userid');
    const userpw = document.querySelector('#userpw');

    if(userid.value == ""){
        alert('아이디를 입력해주세요');
        userid.focus();
        return 0;   // 이 함수를 끝낸다는 의미
    }
    if(userpw.value == ""){
        alert('비밀번호를 입력해주세요');
        userpw.focus();
        return 0;
    }

    // POST로 요청 auth/local/login
    let url = 'http://localhost:3000/auth/local/login';
    
    let options = {
        method:'POST',
        headers :{
            'content-type' : `application/json`,
        },
        body:JSON.stringify({
            userid:userid.value,
            userpw:userpw.value
        })
    }

    let response = await fetch(url, options);       // 어떤 상태값을 받아주는거임 - 성공/실패
   
    let json = await response.json();

    let {result,msg} = json;
    alert(msg);     // 성공 or 실패 msg     // alert로 msg 띄워줌
    if(result){
        // 로그인이 성공됨
        // 로그인 되면 창 닫아야 하니까 open 클래스 지워줘야함
        let layerPopup = document.querySelector('.layerPopup');
        layerPopup.classList.remove('open');
    }else{
        //로그인 실패시 내용 다 지워주고 포커스 이동
        userid.value='';        
        userpw.value='';
        userid.focus();
    }
}

 

★ new 개념들 (꼭 알아두자)

더보기

1. classList.add/remove

// 배경을 클릭 시 로그인창 꺼지게
function popupClose(event){
    if(event.target == this){  // 배경클릭시에만 open 클래스 지우게 
        this.classList.remove('open')
    }
}

classList.add 로 class 를 하나 추가해주었다.

 

반대로
classList.remove('open') 로 class 'open'을 지워주었다.

setAttribute 보다 편하고 좋구만

 

 

 

 

2. function(event)

layerPopup.addEventListener('click',popupClose);

// 배경을 클릭 시 로그인창 꺼지게
function popupClose(event){
    console.log(event); // div layerPopup 이거를 가리킨다.
    console.log(this);  // 내가 클릭한 element
    if(event.target == this){  // 배경클릭시에만 open 클래스 지우게 
        this.classList.remove('open')
    }
}

console.log(event);

클릭한 값에 따라 target 이 바뀐다.

배경을 클릭하면 target: div.layerPopup.open

로그인 창을 클릭하면 target: div.loginPopup

(target을 이용하면 정확히 어떤 부분을 찍었는지 알 수 있다.)

 

 

console.log(this);

항상 <div class="layerPopup open">  즉, 검정 배경을 가리킨다

layerPopup 얘한테 addEventLisner로 이벤트를 줬기 때문에 얘를 기준으로 this가 적용된다.

그래서 여기서의 this는 항상 검정배경이다.

 

이 두개가 같은 값일 때만

class값을 지워서 창을 닫아준다.

 

 

 

 

3. res.json

데이터를 보내준다는 것을 명확히 하고 싶을때 json을 사용한다고 한다.

res.json은 자주 쓰이는 메소드를 구현해놓은 것이라고 보면 됩니다. 
즉 안에 들어있는 데이터들을 자동으로 json 형식으로 바꾸어 보내줍니다.

결론적으로 res.send()와 res.json()은 별반 다를게 없지만,
json형식을 사용한다면 이미 정의되어 있는 res.json()을 사용하는게 효율적입니다

 

 

 

 

4. main.js 에서 post 값(요청) 보내는 부분

일단 js에서 post 값을 보낼 수 있다는걸 오늘 첨 알았음
awiat fetch로 html에서 받아온 값을 server로 post 든 get이든 요청 보낼 수 있다.
만약 post로 한다면 html에서 썼던 form 태그 대신 쓴다고 보면될듯
form 안에서도 action 하고 method 정해줬는데
그걸 대신해서 
let response = await fetch(url, options);
이렇게 
url 이 action 즉 어느 링크로 가야하는지
options 부분이 method 와 보내는 데이터들(name 값들..)
그 값을 담아서 server에서 app.post('auth/local/login')
여기서 받아서 값을 처리한다.

 

 

 

 

 

5. server에서 json 형태로 응답을 main.js 로 보내는 부분
이 부분은 다시 체크 해봐야겠다.

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = req.body;     //npm install body-parser
    console.log(userid, userpw);
    res.json({
        result:true,
        msg:'로그인에 성공했습니다'
    })
})

이 부분이다.

 

 

★ 요청 보낼때 보낼 값을 body에 담을까 header에 담을까?

웹 표준은 body라고 한다.

하지만 간혹 header에 담아서 보내라고 하는 경우도 있으니 알아두자.

더보기

1. headers 에 담아서 보내는 경우

    let url = 'http://localhost:3000/auth/local/login';
    let options ={
        method:'POST',
        headers : {
            'content-type' : 'application/json',
            'data' : JSON.stringfy({
            	userid:userid.value,
            	userpw:userpw.value
              )},
        },
             
    }
    let response = await fetch(url, options);       // 어떤 상태값을 받아주는거임 - 성공/실패
    let json = await response.json();

 

값 받아오기 [server.js]

app.use(bodyParser.json())

app.post('/auth/local/login', (req, res) => {
	// headers에 담긴 데이터를 가져온 부분
		let {userid, userpw} = JSON.parse(req.get('data'))		 
    		console.log(userid, userpw);  
})

string으로 받아서 json으로 되돌려서 사용해야함   
요청쪽에서 get을 사용하면 안에 들어가는 인자값(= 헤더의 속성값)에 접근이 가능하다

[우리가 알고있는 그 get이랑 착각하면 안됨]

 


 

 

2. body에 담아서 보내는 경우

    let url = 'http://localhost:3000/auth/local/login';
    let options ={
        method:'POST',
        headers : {
            'content-type' : 'application/json',
        },
        body:JSON.stringfy({
        	userid:userid.value,
           	userpw:userpw.value
          })
             
    }
    let response = await fetch(url, options);       // 어떤 상태값을 받아주는거임 - 성공/실패
    let json = await response.json();

 

값 받아오기

app.use(bodyParser.urlencoded({extended:flase}));	//npm install body-parser

app.post('/auth/local/login', (req, res) => {
	// body에서 보낸걸 받아옴
   		let { userid, userpw } = req.body;     
    		console.log(userid, userpw);
})

 

 

3. server.js

더보기
const express = require('express');
const cookieParser = require('cookie-parser');
const nunjucks = require('nunjucks');
const bodyParser = require('body-parser');
const ctoken = require('./jwt'); //jwt.js 가져옴
const auth = require('./middleware/auth')   // auth.js 가져옴
const app = express();


app.set('view engine', 'html');
nunjucks.configure('views', {
    express: app,
})

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static('public'));

app.get('/', (req, res) => {
    let { msg } = req.query;
    res.render('index')
})

app.get('/user/info',auth,(req,res)=>{
    res.send(`hello ${req.userid}`);
})

app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;     
    console.log(userid, userpw);
    let result = {};
    //db 접속후 결과 return

    if (userid == 'root' && userpw == 'root') {
        //로그인성공
        result = {
            result: true,
            msg: '로그인에 성공했습니다'
        }

        // 로그인 성공시 토큰 생성하는 내용
        // 여기에 토큰 생성하면 내용이 길어지니 따로 파일을 빼주자 jwt.js
        // 그 다음에 여기서 매서드 호출하자
        let token = ctoken(userid);
        res.cookie('AccessToken',token,{httpOnly:true,secure:true});

    } else {
        //로그인실패
        result = {
            result: false,
            msg: '아이디 패스워드 확인해주세요'
        }
    }
    res.json(result)    // result 값을 보내주는 역할 Boolean 타입
})


app.get('/login', (req, res) => {
    let { id, pw } = req.query;

    if (id == 'root' && pw == 'root') {
        //토큰생성
        let ctoken = token();
        res.cookie('token', ctoken, { httpOnly: true, secure: true, });
        res.redirect('/?msg=로그인성공');
    } else {
        //토큰실패
        res.redirect('/?msg=로그인실패');
    }
})

app.listen(3000, () => {
    console.log('server 3000');
})

 

1. 회원정보 보기

const auth = require('./middleware/auth')   // auth.js 가져옴

app.get('/user/info',auth,(req,res)=>{
    res.send(`hello ${req.userid}`);
})

토큰 검증 후 로그인 완료되어서 이 페이지로 옴 

auth 를 미들웨어로 설정해서 auth.js 에서 로그인 검증 먼저 들어감 잘 되면 next 가 실행되면서 여기로 들어옴

 

1) 로그인이 된 경우

send 로 이런값을 보내줌

 

 

2) 로그인이 안된 경우

auth.js 에서 (미들웨어 때문에 이리로 가게됨)

if (AccessToken == undefined) {   // 토큰이 없다면
        res.redirect('/?msg=로그인을 진행해주세요')
}

 이 부분에 걸려서 이런 값을 띄워준다.

 

 


 

 

2. 로그인 버튼 누른 경우

app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;     //npm install body-parser
    console.log(userid, userpw);
    let result = {};
    //db 접속후 결과 return

    if (userid == 'root' && userpw == 'root') {
        //로그인성공
        result = {
            result: true,
            msg: '로그인에 성공했습니다'
        }

        // 로그인 성공시 토큰 생성하는 내용
        // 여기에 토큰 생성하면 내용이 길어지니 따로 파일을 빼주자 jwt.js
        // 그 다음에 여기서 매서드 호출하자
        let token = ctoken(userid);
        res.cookie('AccessToken',token,{httpOnly:true,secure:true});

    } else {
        //로그인실패
        result = {
            result: false,
            msg: '아이디 패스워드 확인해주세요'
        }
    }
    res.json(result)    // result 값을 보내주는 역할 Boolean 타입
})

 

 


 

 

3. id 와 pw 입력하고 로그인하기 버튼을 누른 경우

app.get('/login', (req, res) => {
    let { id, pw } = req.query;

    if (id == 'root' && pw == 'root') {
        //토큰생성
        let ctoken = token();
        res.cookie('token', ctoken, { httpOnly: true, secure: true, });
        res.redirect('/?msg=로그인성공');
    } else {
        //토큰실패
        res.redirect('/?msg=로그인실패');
    }
})

로그인 성공시에만 토큰을 생성한다.