MongoDB에 저장할 데이터 구조 즉, schema를 작성하고 외부에서 사용할 수있도록 export 했다.
일단은 유저 회원가입 및 로그인에 필요한 내용만 작성했다.
root에서 model 디렉토리를 만들고 그안에 User.js 파일을 생성했다.
// /root/back/model/User.js
const mongoose = require('mongoose');
const saltRounds = 10;
/* userSchema */
const userSchema = new mongoose.Schema(
{
nickname: {
type: String,
maxlength: 10,
trim: true,
unique: true,
// required: true,
},
email: {
type: String,
trim: true,
unique: true,
// required: true,
},
password: {
type: String,
minlength: 5,
trim: true,
required: true,
},
role: {
type: Number,
default: 0,
},
token: {
type: String,
},
tokenExp: {
type: Number,
},
},
{ autoIndex: false },
);
const User = mongoose.model('User', userSchema);
module.exports = { User };
회원가입 및 로그인 기능을 구현하면서 암호화와 토큰생성하는 법을 자세하게 작성해놓은 포스트를 찾게되어 적용해봤다.
Bycrypt와 JWT를 설치하고 아래와 같은 과정을 거치게 만들었다.
// /root/back/model/User.js
/* Bcrypt */
// 비밀번호 암호화
userSchema.pre('save', function (next) {
const user = this;
// salt를 이용해서 비밀번호 암호화한 후 보내줌 (비밀번호와 관련될 때만)
if (user.isModified('password')) {
bcrypt.genSalt(saltRounds, (err, salt) => {
if (err) return next(err);
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) return next(err);
user.password = hash;
next();
});
});
} else {
// 그 외에는 그냥 내보냄
next();
}
});
// /root/back/model/User.js
// 로그인 - 비밀번호 비교
userSchema.methods.comparePassword = function (plainPassword, cb) {
// 입력된 비밀번호와 데이터베이스에 있는 암호화된 비밀번호가 같은지 확인(비교) -> 평문을 암호화해서 비교
bcrypt.compare(plainPassword, this.password, (err, isMatch) => {
if (err) return cb(err);
cb(null, isMatch); // 즉, true
});
};
// 로그인 - 토큰 생성
userSchema.methods.generateToken = function (cb) {
const user = this;
// jsonwebtoken을 이용해서 토큰 생성
const token = jwt.sign(user._id.toHexString(), 'secretToken');
// user._id + 'secretToken' = token 을 통해 토큰 생성
// 토큰 해석을 위해 'secretToken' 입력 -> user._id 가 나옴
// 토큰을 가지고 누구인지 알 수 있는 것
user.token = token;
user.save(function (err, user) {
if (err) return cb(err);
cb(null, user);
});
};
// /root/back/middleware/auth.js
const { User } = require('../model/User');
let auth = (req, res, next) => {
// 클라이언트 쿠키에서 토큰을 가져온다.
let token = req.cookies.x_auth;
// 토큰을 복호화한 후 유저를 찾는다.
User.findByToken(token, (err, user) => {
if (err) throw err;
if (!user) return res.status(400).json({ isAuth: false, error: true });
req.token = token;
req.user = user;
next();
});
// 유저가 있으면 인증 OK!
// 유저가 없으면 인증 NO!
};
module.exports = { auth };
// /root/back/model/User.js
// auth 인증 - 복호화(토큰 디코드)
userSchema.statics.findByToken = function (token, cb) {
const user = this;
jwt.verify(token, 'secretToken', (err, decoded) => {
// 유저 아이디를 이용해서 유저를 찾은 다음에
// 클라이언트에서 가져온 token과 DB에 보관된 토큰이 일치하는지 확인
user.findOne({ _id: decoded, token: token }, function (err, user) {
if (err) return cb(err);
cb(null, user);
});
});
};
token: ""
으로 지워주게 되면 자동으로 인증이 풀리게 되어 로그아웃된다.// /root/back/routes/api.js
router.post('/users/logout', auth, (req, res, next) => {
User.findOneAndUpdate({ token: req.user.token }, { token: '' }, (err, user) => {
if (err) {
return res.json({ success: false, err });
}
return res.status(200).send({
success: true,
});
});
});
유저의 접속 상태 관리를 위해 SWR과 ReactQuery중 고민하다가 뭐가 더 추세인지 여러 사람에게 물어봤더니 ReactQuery가 추세라고 하여 사용해봤다.
물론 사용하기 위해 또 공부.. 또또 공부..
https://velog.io/@ahuuae/Library-React-Query
ReactQuery에 대해 찾아서 작성한 포스트이다.
처음엔 그저 사용법과 코드만 알고 무작정 프로젝트에 끼얹다가 혼났다. 언제 useQuery를 사용하는지 언제 useMutationd을 사용하는지 알게됐고 일단은 로그인 기능에만 사용해봤다.
// /root/front/src/apis/loginApi.js
import axios from 'axios';
const loginApi = (body) => {
axios
.post('/api/users/login', body, {
withCredentials: true,
})
.then((res) => {
console.log('로그인 성공');
})
.catch((error) => {
console.log('로그인 실패');
console.log(error.response);
});
};
export default loginApi;
import loginApi from '@apis/loginApi';
import useInput from '@hooks/useInput';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useEffect, useState } from 'react';
const LogIn = () => {
const navigate = useNavigate();
const [logInError, setLogInError] = useState(false);
const [email, onChangeEmail] = useInput('');
const [password, onChangePassword] = useInput('');
/* useMutation */
const loginMutation = useMutation(loginApi, {
onMutate: (variable) => {
console.log('onMutate', variable);
},
onError: (error, variable, context) => {
console.log('Error');
},
onSuccess: (data, variables, context) => {
// console.log('Success', data, variables, context);
},
onSettled: () => {
// console.log('End');
},
});
/* useEffect */
useEffect(() => {
if (isLoggedIn) navigate('/'); // 로그인 성공하면 메인페이지로 이동
}, [isLoggedIn, navigate]);
/* Submit */
const onSubmit = useCallback(
(e) => {
e.preventDefault();
setLogInError(false);
loginMutation.mutate({ email, password }); // mutation함수로 보낼 변수
},
[email, password, loginMutation],
);
return (
// 로그인용 UI...
);
};
export default LogIn;
하지만 늦게 안 사실.. useMutation을 사용해서 돌려 받는 값으로 후처리를 할 수 있을 것 같다.
주말에 수정해봐야지. 일단 성공은 했다.
이것 또한 늦게 안 사실.. ReactQuery만으로 유저의 접속상태를 관리할 수 있다.
근데 바보처럼 Redux까지 사용해가면서 환경을 만들고 프로젝트에 끼얹고.. 그렇게 로그인 상태 변경해보는 짓을 해버렸다.
// /root/front/src/reducers/index.js
/* root reducer */
import loginReducer from './loginReducer';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
loginReducer,
});
export default rootReducer;
// /root/front/client.jsx
const store = createStore(rootReducer, enhancer);
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>,
);
// /root/front/src/reducers/loginReducer.js
export const USER_LOGIN = 'USER_LOGIN';
export const loginSuccess = (isLoggedIn) => ({ type: USER_LOGIN, isLoggedIn });
const initalState = {
isLoggedIn: false,
};
const loginReducer = (state = initalState, action) => {
switch (action.type) {
case USER_LOGIN:
return {
...state,
isLoggedIn: true,
};
default:
return state;
}
};
export default loginReducer;
const loginMutation = useMutation(loginApi, {
onMutate: (variable) => {
console.log('onMutate', variable);
dispatch(loginSuccess());
},
...
이번 주말에는 ReactQuerya만을 사용해서 유저 인증 서비스를 다 구축해보자