통합구현 #2 - Token

김형우·2022년 3월 14일
0

mongoose + vue.js

목록 보기
3/15

토큰 발행 라이브러리
CMD> npm i jsonwebtoken --save

CMD> nodemon --inspect ./bin/www

0. token/auth.js

  1. 토큰 사용을 위한 라이브러리 설치
    : CMD> npm i jsonwebtoken --save

  2. 라이브러리 사용을 위한 객체생성
    : const jwt = require('jsonwebtoken');

  3. 외부(다른파일)에서도 사용할수있게 modul.exports로 만듦.
    : 이때 auth.js 해당 페이지에서도 사용할수있게 객체도 생성해줌
    : => const self = module.exports = { }

  4. securityKEY
    : salt값, 토큰값을 가공할때 KEY가 되는 값

  5. options
    5-1. algorithm
    : 알고리즘 명, 메뉴얼대로 HS256으로 함
    5-2. expiresIn
    : 만료기간, 10m = 10분, 10h = 10시간
    5-3. issuer
    : 발행자, 임의로 ds라고 함

  6. checkToken
    : 프론트엔드에서 오는 토큰의 검증부분
    6-1. 토큰은 headers에 담겨서 넘어옴 key는 auth
    : postman 적용 시 headers에 auth라는 이름으로 로그인 할때 발급되는 토큰값을 넣음
    6-2. 토큰이 있느냐 없느냐
    : if (token === null)
    6-3. 본격 검증
    : 발행시 sign <=> verify 검증시
    : 이 시점(토큰복원)에 오류가 가장 많이 난다.
    : 오류는 모두 catch로 간다.
    6-4. 토큰이 정확한가
    : 로그인 할때 토큰 발행함
    : 발행할때 토큰에 넣은 USERID, USERNAME키가 존재하는지 확인
    : 없다면 '토큰복원실패'
    : 토큰을 복원verify해서 sessionData에 담음
    : const sessionData = jwt.verify(token, self.securityKEY);
    6-5. 모든 조건을 통과했으면 routes에서 사용할수 있도록 body안에 해당 값을 담아서 전달(next();)
    : req.body.USERID = sessionData.USERID;
    : req.body.USERNAME = sessionData.USERNAME;

  7. catch에서 잡힌 오류
    : jwt에서 제공하는 오류메세지를 로그화시켜서 쉽게 파악할수 있도록 한다.
    7-1. 토큰인증실패
    : if(e.message === 'invalid signature') { }
    7-2. 토큰시간만료
    : if(e.message === 'jwp expired') { }
    7-3. 유효하지않은토큰
    : if(e.message === 'invalid token') { }
    7-4. 이밖의 메세지 = '유효하지않은토큰'

const jwt = require('jsonwebtoken');

// 
// module.exports = { }
const self = module.exports = {
    // 토큰 발행 salt값
    securityKEY : 'aro9515',

    // 토큰 발행에 필요한 옵션들
    options : {
        algorithm : 'HS256',
        expiresIn : '20m',
        issuer    : 'ds',
    },

    // 프론트엔드에서 오는 토큰 "검증" 부분
    // 검증!
    checkToken : async(req,res,next) => {
        try {
            const token = req.headers.auth; // 키는 auth;
            // 1. 토큰이 있냐없냐
            if (token === null) {
                return res.send({status:0, result:'토큰없음'});
            }
            // 발행시 sign <=> verify 검증시
            // 발행된 토큰, 보안키
            // 이 시점 (토큰복원)에 오류가 가장 많이 난다.
            // 오류는 모두 catch로 간다
            const sessionData = jwt.verify(token, self.securityKEY);

            // 2. 토큰이 정확하냐
            // USERID, USERNAME키가 존재하는지 확인
            // 발행할때 이렇게 넣었기 때문.
            if (typeof sessionData.USERID === 'undefined') {
                return res.send({status:0,result:'토큰복원실패'})
            }
            if (typeof sessionData.USERNAME === 'undefined') {
                return res.send({status:0,result:'토큰복원실패'})
            }

            // 위의 if문을 다 통과했으면 완료된거
            // routes/member.js에서 사용가능하도록 전달
            req.body.USERID = sessionData.USERID;
            req.body.USERNAME = sessionData.USERNAME;

            // routes/member.js로 전환
            next();
        } catch (e) {
            console.error(e);
            if(e.message === 'invalid signature') {
                return res.send({status:-1,result:'토큰인증실패'})
            }
            if(e.message === 'jwp expired') {
                return res.send({status:-1,result:'토큰시간만료'})
            }
            if(e.message === 'invalid token') {
                return res.send({status:-1,result:'유효하지않은토큰'})
            }
            return res.send({status:-1,result:'유효하지않은토큰'})
        }
    }
}

1. 회원가입

  • insert
  1. password 부분은 HASH를 사용해서 변환시켜서 넣는다.
    : const hashPw = crypto.createHmac('sha256', req.body.id,).update(req.body.pw).digest('hex');
    1-1. sha256 => 알고리즘 이름
    1-2. req.body.id => 고유값
    1-3. .digest('hex') => 16진수로 변환

  2. password는 HASH된 값으로 들어감

  3. password가 동일 한지 검사하기위해서는 똑같이 HASH 시켜서 비교할수밖에 없음
    : 단방향, 복원안됨

// 127.0.0.1:3000/member/insert
// { "_id" : "aa", "name" : "aa", "password" : "aa", "email" : "a@a.com", "age" : 23 }
router.post('/insert', async function(req, res, next) {
    try {
        // HASH 사용
        // sha256 => 알고리즘 이름
        // req.body.id => 고유값
        // .digest('hex') => 16진수로 변환
        // 단방향, salt값이 있어도 복원안됨
        // 똑같이 hash시켜서 비교해야함
        // => salt값 (키값, 첨가물)
        const hashPw = crypto.createHmac('sha256', req.body.id,).update(req.body.pw).digest('hex');

        // 빈 member 객체 생성
        var member = new Member();
        // member._id = req.body.id;        
        member['_id'] = req.body.id;        
        member.name = req.body.name;        
        member.password = hashPw;        
        member.email = req.body.email;        
        member.age = Number(req.body.age);        
        // member.regdate = new Date;
        
        const result = await member.save();
        console.log("/insert/result ===> ",result);
        if (result._id != '') {
            return res.send({status:200});            
        }
        return res.send({status:0});
    } catch (e) {
        console.error(e);
        return res.send({status:-1});
    }
});

2. id 중복확인

  • idcheck
  1. findOne({조건})으로 동일한 아이디가 존재하는지 확인
  2. 존재하면 중복 없으면 중복아님
// 127.0.0.1:3000/member/idcheck?id=aa
router.get('/idcheck', async function(req, res, next) {
    try {
        // 아이디에 해당하는 값을 조회
        const result = await Member.findOne({_id : req.query.id});
        console.log("/idcheck/result ===> ",result);
        if (result !== null) {
            return res.send({status:200,result:1,msg:'중복된 아이디 있음'});
        }
        return res.send({status:200,result:0,msg:'중복된 아이디 없음'});

    } catch (e) {
        console.error(e);
        return res.send({status:-1});
    }
});

3. 로그인

  1. 회원가입(insert)때, password를 HASH 시켜서 추가했음

  2. _id와 password가 모두 일치해야함

  3. password는 insert때와 똑같이 HASH시켜서 서로 같은지 확인해야함
    : 한번 HASH시킨 값은 복원 불가능하기 때문

  4. findOne({조건}) 부분에 조건에 해당하는 변수 만들기
    : const query = {$and : [{_id : req.body.id, password: hashPw}]};1
    : _id와 HASH된 password가 모두 일치하는것이 findOne의 조건

  5. 일치하는 항목을 찾은 시점이 로그인 성공시점
    : const result = await Member.findOne(query);

  6. result가 존재하면(null이 아니라면), session에 정보를 추가함
    : 정보 = 토큰
    : 토큰에 USERID와 USERNAME도 넣는다.
    : const sessionData = { USERID : result._id, USERNAME : result.name };

  7. 세션에 추가할 값, 보안키(salt값 포함), 옵션
    : const token = jwt.sign(sessionData,auth.securityKEY, auth.options);

  8. 이후 해야하지만 생략한것
    : DB에 member8컬렉션에 insert시점부터 token이라는 키를 만듦
    : 거기에 token값을 넣어서(수정)
    : 일치 여부 비교.

// 127.0.0.1:3000/member/login
// {"id":"aa", "pw":"aa"}
router.post('/login', async function(req, res, next) {
    try {
        // get  => req.query.키
        // post => req.body.키
        // 암호 hash
        const hashPw = crypto.createHmac('sha256', req.body.id,).update(req.body.pw).digest('hex');
        
        // DB연동 키 (_id, password)
        const query = {$and : [{_id : req.body.id, password: hashPw}]};
        const result = await Member.findOne(query);
        console.log(result);
        if (result !== null) {
            // 로그인 성공 시점
            // session에 정보를 추가함 
            // => vue.js에서는 세션에 접근할수 없다. 다른서버이기때문
            // = 같은 서버가 아니기때문에 세션을 확인할 방법이 없음.
            // 때문에 token(출입할수 있는 키)을 발행

            // 세션에 추가할 값, 보안키(salt값 포함), 옵션
            const sessionData = {
                USERID : result._id, 
                USERNAME : result.name
            };
            const token = jwt.sign(sessionData,auth.securityKEY, auth.options);

            // DB에 token이라는 키로 수정함
            // => member8 안에 token이라는 키를 만들어서 거기에 token값을 넣어서 
            // 일치 여부 비교.

            return res.send({status : 200, result : result, token : token, msg : '로그인 성공'})
        }
        return res.send({status : 0, result : 0, msg : '로그인 실패'});
    } catch (e) {
        console.error(e);
        return res.send({status : -1});
    }
});
profile
The best

0개의 댓글