토큰 발행 라이브러리
CMD> npm i jsonwebtoken --save
CMD> nodemon --inspect ./bin/www
토큰 사용을 위한 라이브러리 설치
: CMD> npm i jsonwebtoken --save
라이브러리 사용을 위한 객체생성
: const jwt = require('jsonwebtoken');
외부(다른파일)에서도 사용할수있게 modul.exports로 만듦.
: 이때 auth.js 해당 페이지에서도 사용할수있게 객체도 생성해줌
: => const self = module.exports = { }
securityKEY
: salt값, 토큰값을 가공할때 KEY가 되는 값
options
5-1. algorithm
: 알고리즘 명, 메뉴얼대로 HS256으로 함
5-2. expiresIn
: 만료기간, 10m = 10분, 10h = 10시간
5-3. issuer
: 발행자, 임의로 ds라고 함
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;
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:'유효하지않은토큰'})
}
}
}
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진수로 변환
password는 HASH된 값으로 들어감
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});
}
});
findOne({조건})
으로 동일한 아이디가 존재하는지 확인// 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});
}
});
회원가입(insert)때, password를 HASH 시켜서 추가했음
_id와 password가 모두 일치해야함
password는 insert때와 똑같이 HASH시켜서 서로 같은지 확인해야함
: 한번 HASH시킨 값은 복원 불가능하기 때문
findOne({조건})
부분에 조건에 해당하는 변수 만들기
: const query = {$and : [{_id : req.body.id, password: hashPw}]};1
: _id와 HASH된 password가 모두 일치하는것이 findOne의 조건
일치하는 항목을 찾은 시점이 로그인 성공시점
: const result = await Member.findOne(query);
result가 존재하면(null이 아니라면), session에 정보를 추가함
: 정보 = 토큰
: 토큰에 USERID와 USERNAME도 넣는다.
: const sessionData = { USERID : result._id, USERNAME : result.name };
세션에 추가할 값, 보안키(salt값 포함), 옵션
: const token = jwt.sign(sessionData,auth.securityKEY, auth.options);
이후 해야하지만 생략한것
: 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});
}
});