오늘의 목표
✔️ 개인 과제 구현
⏱️ 오늘의 일정
10:00 ~ 11:00 - 스탠다드반 CS 강의
11:00 ~ 19:00 - 개인과제 구현
19:00 ~ 20:30 - 챌린지반 수업
📜 스탠다드 반 CS 강의
10:00 ~ 11:00 - 스탠다드 반 CS 강의
매주 화목에 진행하는 스탠다드반 수업 강의가 있어서 참여했다.
오늘 내용은 이더넷, 허브, 스위치, Mac 주소에 관해 배웠다.
📜 개인과제 구현
11:00 ~ 19:00 개인과제 구현
오늘은 속도가 좀 붙어서 로그인 기능과 캐릭터 생성, 캐릭터 삭제, 캐릭터 조회, 아이템 생성 까지 구현했다.
로그인 기능에서 고민이 있던 부분을 포스팅하고자 한다.
처음에는 로그인 할때 아이디와 비밀번호가 맞으면 AccessToken을 생성해 반환해주는 걸로 우선 구현을 했다.
이렇게 구현하다가는 만약 재로그인한 대상이 해당 유저가 아닐 경우에는? 어떻게 해야할까? 라고 생각이 들어서 재로그인할때는 AccessToken을 살펴보기로 하고 구현했다.
내가 생각한 로그인 방식은 다음과 같다.
1. 로그인 요청한 대상이 AccessToken을 가지고 오지 않았으면
1.1 처음 발급 하는것이므로 AccessToken과 RefreshToken을 발급하고 로그인 성공 처리한다.
2. 로그인 요청한 대상이 AccessToken을 가지고 오면
2.1 해당 AccessToken이 유효한지 판단한다.
2.1.1 AccessToken이 유효하면 로그인 성공 처리한다.
2.1.2 AccessToken이 유효하지 않으면, RefreshToken을 가지고 있는지 확인한다.
2.1.2.1 RefreshToken을 가지고 있으면, RefreshToken이 유효한지 판단한다.
2.1.2.1.1 RefreshToken이 유효하면, AccessToken을 재발급하고 로그인 성공 처리한다.
2.1.2.1.2 RefreshToken이 유효하지 않으면, RefreshToken과 AccessToken을 재발급하고 로그인 성공 처리한다.
2.1.2.2 RefreshToken을 가지고 있지않으면, RefreshToken을 발급하고 AccessToken을 재발급하고 로그인 성공처리
위처럼 생각하고 구현해보니 어떻게해도 무조건 로그인은 성공처리가 되는것이 아닌가?
다시한번 생각하니 로그인 처리에서는 AccessToken과 RefreshToken을 발급 하는것이 목적인것이고 ( 아이디와 비밀번호만 맞으면 ), 클라이언트에서는 AccessToken을 가지고 로그인을 제외한 다른 작업을 할때, '검증' 하는 것을 목적으로 삼으면 되는것이니까.
로그인 처리에서는 앞서 언급했듯이 아이디와 비밀번호만 맞으면 로그인 성공처리로 해주면 되겠구나라는 생각이 들어서 그대로 진행했다.
로그인 처리를 제외하고는 다른 캐릭터 생성, 캐릭터 삭제 등 인증이 필요한 곳에는 AccessToken으로 검증을 해 해당 값이 유효하지 않으면 바로 에러를 반환하도록 구현했다.
위처럼 생각한 것이 맞는지는 확신할 수 없다.
위 글을 보면 맞는거 같기도 하고.. 애매..
내일 과제를 완료하면 따로 튜터님한테 가서 물어보긴 해야겠다.
로그인
// 로그인
router.post('/sign-in', async (req, res, next) => {
const { id, password } = req.body;
const user = await prisma.users.findFirst({
where: { id }
});
// 아이디와 비밀번호 검사
if (!user) {
return res.status(401).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
}
else if (!(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: `비밀번호가 일치하지 않습니다.` });
}
// 재로그인할때 필요한 accessToken을 가져옴
const c2sAccessToken = req.cookies.accessToken;
let s2cAccessToken = 0;
let s2cRefreshToken = 0;
// accessToken 발급 여부 판단
let newAccessToken = false;
if (!c2sAccessToken) // 액세스 토큰이 없음
{
// 액세스 토큰 새 발행
newAccessToken = true;
}
else // 액세스 토큰이 있음
{
// 액세스 토큰 유효한지 확인
// 유효하면 로그인 성공
const [tokenType, token] = c2sAccessToken.split(' ');
// tokenType이 맞는지 확인
if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
return res.status(404).send('not found');
}
// 다른 유저의 AccessToken을 가지고 왔을 경우
// 현재 로그인한 유저를 대상으로 AccessToken 재발행
const myToken = CreateAccessToken(id);
if (myToken !== c2sAccessToken)
{
newAccessToken = true;
}
else
{
// 나의 AccessToken을 가지고 오면 유효한지 확인
const payload = ValidateToken(token, process.env.ACCESS_TOKEN_SECRET_KEY);
if (!payload) // 액세스 토큰이 유효하지 않음
{
// 액세스 토큰 새 발행
newAccessToken = true;
}
}
}
// 액세스 토큰 새로 발급
if (newAccessToken) {
// DB에서 리프레시 토큰을 읽어옴
const dbRefreshToken = await prisma.refreshTokens.findFirst({
where: { userId: id },
select: {
token: true
}
});
// DB에 리프레시 토큰이 없음
if (dbRefreshToken == null)
{
// 액세스 토큰과 리프레시 토큰을 새로 발급
s2cAccessToken = CreateAccessToken(id);
s2cRefreshToken = CreateRefreshToken(id);
// 리프레시 토큰을 DB에 저장
const newDBRefreshToken = await prisma.refreshTokens.create({
data: {
userId: id,
token: s2cRefreshToken
}
});
// 쿠키 전달
res.cookie('accessToken', s2cAccessToken);
res.cookie('refreshToken', s2cRefreshToken);
}
else
{
// DB에 리프레시 토큰이 있음
const [tokenType, token] = dbRefreshToken.token.split(' ');
// tokenType이 맞는지 확인
if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
return res.status(404).send('not found');
}
// 리프레시 토큰이 유효한지 확인
const dbRefreshTokenCheck = ValidateToken(token, process.env.REFRESH_TOKEN_SECRET_KEY);
if (dbRefreshTokenCheck) // 리프레티 토큰이 유효함
{
// 액세스 토큰 발급
s2cAccessToken = CreateAccessToken(id);
// 액세스 토큰 전달
res.cookie('accessToken', s2cAccessToken);
}
else // 리프레티 토큰이 유효하지 않음
{
// 액세스 토큰과 리프레시 토큰을 새로 발급
s2cAccessToken = CreateAccessToken(id);
s2cRefreshToken = CreateRefreshToken(id);
// 리프레시 토큰을 DB에 업데이트
const newDBRefreshToken = await prisma.refreshTokens.update({
where: { userId: id },
data: {
token: s2cRefreshToken
}
});
// 쿠키 전달
res.cookie('accessToken', s2cAccessToken);
res.cookie('refreshToken', s2cRefreshToken);
}
}
}
return res
.status(200)
.json({ message: `${id}로 로그인 성공` });
});
캐릭터 생성, 캐릭터 삭제, 캐릭터 조회에서는 authMiddleware를 통해 검증하도록 구현했다.
export default async function (req, res, next) {
const c2sAccessToken = req.cookies.accessToken;
if (c2sAccessToken === undefined) {
return res.status(404).json({ message: 'not found' });
}
const [tokenType, accessToken] = c2sAccessToken.split(' ');
if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
return res.status(401).json({ message: '토큰 타입이 일치하지 않습니다.' });
}
// 토큰 검증
const decodedToken = ValidateToken(accessToken, process.env.ACCESS_TOKEN_SECRET_KEY);
if (!decodedToken) {
return res.status(401).json({ message: '토큰이 만료되었습니다. 로그인을 다시 해주세요' })
}
const id = decodedToken.id;
const user = await prisma.users.findFirst({
where: {
id : id
}
});
if (!user) {
return res.status(401).json({message:`${id} 사용자를 찾을수 없습니다.`})
}
req.user = user;
next();
}
로그인한 유저가 AccessToken을 들고오지 않았을 경우, TokenType이 일치하지 않을 경우,
그리고 Token이 만료할 경우 에러를 반환하도록했다.
'내일배움캠프' 카테고리의 다른 글
[내일배움캠프][TIL] 28일차 - 개인과제 끝 (0) | 2024.09.13 |
---|---|
[내일배움캠프][TIL] 27일차 - 개인과제, Node.js 강의 듣기 (4) | 2024.09.12 |
[내일배움캠프][TIL] 25일차 - Node.js 강의, 개인과제 (0) | 2024.09.09 |
[내일배움캠프][TIL] 24일차 - Node.js 강의, 개인과제 (0) | 2024.09.07 |
[내일배움캠프][TIL] 23일차 - Node.js 강의, 챌린지반 OT (1) | 2024.09.05 |