유효성 검사를 통과했다 != 로그인에 성공했다
유효성 검사를 통과했다 => 이 값을 서버에 전달할 수 있겠다
🌟 react 👉 express 👉 mysql
값이 있을때 : 응답(200)
값이 없을때 : 응답은 서버 만드는사람 마음대로(404나 오류 메세지)
login.js
import { Link } from "react-router-dom";
import { AuthBody, AuthBox, AuthFooter, AuthForm, BgImg, Button, CancelIcon, ErrMsg, Input, InputBoxWrap, Line, LogoImg, Wrap } from "../../styles/auth/auth.styles";
import CloseIcon from '@mui/icons-material/Close';
import { useState } from "react";
const LoginPage = () => {
// 유효성 검사
// 이메일 입력시 비어있으면안됨, 이메일형식(@포함)
// 비밀번호입력시 비어있으면 안됨, 6자리 이상()
// submit 할때도 이메일, 비밀번호 잘 입력되었는지 확인
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailErrMsg, setEmailErrMsg] = useState('');
const [passwordErrMsg, setPasswordErrMsg] = useState('');
const onEmailChange = (e) => {
const emailText = e.target.value;
setEmail(emailText);
if (emailText === '') {
setEmailErrMsg('이메일은 필수 입력 값입니다.');
} else if (!emailText.includes('@')) {
setEmailErrMsg('이메일 형식을 지켜주세요');
} else {
setEmailErrMsg('');
}
}
const onPasswordChange = (e) => {
const passwordText = e.target.value;
setPassword(passwordText);
if (passwordText === '') {
setPasswordErrMsg('비밀번호는 필수 입력 값입니다.');
} else if (passwordText.length < 6) {
setPasswordErrMsg('최소 6글자 이상으로 작성해주세요');
} else {
setPasswordErrMsg('');
}
}
const onLoginSubmit = (e) => {
e.preventDefault();
let check = true;
if (email === '') {
setEmailErrMsg('이메일은 필수 입력 값입니다.');
check = false;
} else if (!email.includes('@')) {
setEmailErrMsg('이메일 형식을 지켜주세요');
check = false;
} else {
setEmailErrMsg('');
}
if (password === '') {
setPasswordErrMsg('비밀번호는 필수 입력 값입니다.');
check = false;
} else if (password.length < 6) {
setPasswordErrMsg('최소 6글자 이상으로 작성해주세요');
check = false;
} else {
setPasswordErrMsg('');
}
if (check) {
alert('로그인 유효성 검사가 끝나면 해야할 코드');
}
}
return (
<BgImg>
<Wrap>
<CancelIcon><CloseIcon /></CancelIcon>
<AuthBox>
<LogoImg src="/logo.svg" alt="logo" />
<AuthBody>
<AuthForm onSubmit={onLoginSubmit}>
<InputBoxWrap>
<div className="input-box">
<Input onChange={onEmailChange} type="text" placeholder="아이디" />
<ErrMsg>{emailErrMsg}</ErrMsg>
</div>
<div className="input-box">
<Input onChange={onPasswordChange} type="password" placeholder="비밀번호" />
<ErrMsg >{passwordErrMsg}</ErrMsg>
</div>
</InputBoxWrap>
<Button>로그인 하기</Button>
</AuthForm>
<Line className="line"></Line>
<AuthFooter>
<Link to="">이메일 찾기</Link>
<Line className="line"></Line>
<Link to="">비밀번호 찾기</Link>
<Line className="line"></Line>
<Link to="/join">회원가입 하기</Link>
</AuthFooter>
</AuthBody>
</AuthBox>
</Wrap>
</BgImg>
);
}
export default LoginPage;
📌
express
는mysql
에서 해당 사용자 있는지 확인 ❗️ (57:00/10/02)
사용자가 있다면,
jwt 토큰
발급하여react로 전달
react
는jwt토큰
저장해놓고
앞으로 로그인 한 사람만접근
할 수 있는 페이지가 있다면
jwt토큰
을 함께 담아서 요청
express에서는
jwt토큰
을 받아서,토큰
이 유효한지 검사하여,
해당 📝토큰이 유효하면응답
하고,
📝 유효하지 않으면오류 발생
(권한이 없습니다 로그인을 다시해주세요)
JS 객체에다 저장하는게 가장 편리- 객체를 암호화해서 토큰을 만들 수 있음
공식문서 👉 https://jwt.io/
설치
npm install jsonwebtoken
import
const jwt = require("jsonwebtoken"); // 옛날방식의 import
🌟 sign
이라는 함수는 암호화라는 토큰을 만드는 함수
먼저,
암호화를 시킬때
어떤 데이터를 암호화
시킬건지 말해준다
📝 문법
:
jwt.sign({payload(데이터가 들어가야하는 부분},'tokenkey',{언제까지 유효한지 설정가능})
//ex
jwt.sign( {email:'abc@test.com'},'tokenpw',{{expiresIn:'0.1s'}} )
📌 payload
가 들어가야하는 부분이 첫번쨰 {}
객체
📌 key
비밀번호 ' '
가 들어간다
📌 {expiresIn:'0.1s'}
언제까지 유효한지 설정가능
lat
- 만든시간 issued at
exp
- 유효한시간 expired
example
app.js
const jwt = require("jsonwebtoken");
app.get('/api/jsonwebtokentest', (req, res) => {
let myToken = jwt.sign({ email: 'abc@test.com' }, 'tokenpw', { expiresIn: '1hr' })
console.log(myToken);
// let decoded = jwt.decode(myToken );
// console.log(decoded);
let verify = jwt.verify(myToken, 'tokenpw');
console.log(verify);
res.json('응답끝');
});
login.js
1:00
import { useNavigate } from "react-router-dom";
const LoginPage = () => {
const navigate = useNavigate();
const onLoginSubmit = async (e) => {
e.preventDefault();
let check = true;
if (check) {
// 로그인한 회원 조회
try {
let res = await axios.post('/api/login', { email, password });
alert(res.data.accessToken);
// 로컬스토리지에 저장
localStorage.setItem('accessToken', res.data.accessToken);
//전역상태변수(App.js에있는 accessToken)에 저장
setAccessToken(res.data.accessToken);
navigate('/', { replace: false });
} catch (err) {
console.log(err);
if (err.response.status === 404) {
alert('아이디 또는 비밀번호를 확인해주세요');
}
}
}
}
export default LoginPage;
app.js
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
// mysql에서 해당 email 존재하는지
try {
let sql = 'SELECT email, pw FROM tbl_users WHERE email=?';
const [rows, fields] = await pool.query(sql, [email]);
console.log(rows);
if (rows.length === 0) { 🌟 //rows가 비어있으면, //이메일 실패
res.status(404).json('로그인 실패!');
return;
}
// 사용자가 로그인할때 입력한 "일반 비밀번호랑", "암호화되어 저장된 비밀번호"랑 서로 "같은지" 검사
// 일반비밀번호: password
// 암호화된 비밀번호 : rows[0].pw
if (!bcrypt.compareSync(password, rows[0].pw)) {
// 🌟이메일은 맞췄지만, 비밀번호는 틀렸을때
res.status(404).json('로그인 실패!');
return;
}
// 🌟 로그인이 성공 했다면 ?
// jwt 토큰 만들기
// payload에는 {email:'로그인한사람이메일'}
// 1시간짜리 유효한 토큰으로 만들기
const accessToken = jwt.sign({ email: rows[0].email }, process.env.JWT_SECRET, { expiresIn: '1h' }); //process.env.JWT_SECRET--> .env.local 에 비번설정하고 가져오기
console.log(accessToken);
res.json({ accessToken }); // 만든 토큰을 "객체"에 담아서 🌟 리액트로 전달 (login.js로)
} catch (err) {
res.status(500).json('mysql에서 오류발생');
}
})
1:44
**회원가입할때**
리액트 mysql
ab@ab.com ab@ab.com //이메일그대로 입력
123456 eyJlbWFpbCI6ImFiY0B0ZXN0LmNvbSIsImlhdCI6MTY5NjQwNDYzNiwiZXhwIjoxNjk2NDA4MjM2fQ //비밀번호 암호화되서 입력
**로그인할때**
ab@ab.com //이메일 입력하면 찾아준다.
123456 //비밀번호는 회원가입한거처럼 될까? 놉! 암호화된결과물이 서로 다르다.
gdJlbWFpbCI6ImFiY0B0ZXN0LmNvbSIsImlhdCI6MTY5NjQwNDYzNiwiZXhwIjoxNjk2NDA4MjM2fQ
//암호화해서 회원가입할때 암호화된 애랑 비교하면 되지 않을까??? 생각할수있지만, bcrypt 는 보안이 철저해서 똑같이 암호화가 되지 않는다.
그래서, **로그인할때**
ab@ab.com 이메일 가진 녀석을 찾아서
암호화된 비밀번호 가져오기! 👉 (bcrypt함수 사용해서)
bcrypt(일반 비밀번호, 암호화 된 비밀번호) //일반 비밀번호랑 암호화 된 비밀번호를 확인한다
비밀번호를 맞게쓰면 토큰이 나온다
🔎 그런데, 이 토큰을 변수에 저장하면 문제가생길수있다❗️
👉LoginPage
함수 안에 저장해봤자 다른 페이지로 이동하면 사라져버리기 때문에 ❗️
그래서, 브라우저에 저장공간이 있다(local storage
,Session storage
,Cookies
)
🤨 어떤걸 사용하든 본인의 취향 차이(서로 약간의 장단점은 있을수 있음)
local storage
👉 페이지가 바껴도 브라우저에서 가지고있다.
cookies
👉 조금더 세세하게 설정 가능함 key와 value 로(name, value, domain,...)
📝 변수에서 만들면 그 페이지에서밖에 못쓰니까, local storage로 저장해서 브라우저에 저장한다.
📝 토큰을 loginPage에 저장을한다고 가정하면❓
📝 페이지가 dashboardPage로 바뀌면 ❓
👉 👉 토큰이 사라진걸 볼수있다 🔎
그런데, 여기서 토큰을 제일 상위 컴포넌트인, App.js
에 넣어주면 🤨❓
짜잔 🌟 App.js
에서 저장한 토큰이 loginpage
에도 사용가능하네❓
짜잔 🌟 App.js
에서 저장한 토큰이 dashboardpage
에도 사용가능하네❓
🌟 그래서 최상위(App.js) component에 state변수
를 저장하면 자식
에서도 사용가능 ❗️❗️❗️ yeah 😇 그림으로 하니까 설명이 더 잘되쥬?👍
자 그럼 이제 더 자세한설명으로 가보자 🔎
state변수
는 만들어져있는 컴포넌트 안에서밖에 사용을 못한다
그런데❗️token
은a컴포넌트
,b컴포넌트
,c컴포넌트
에서 모두 사용을 해야하는데..?
📝 전역 스테이트변수는 스테이변수는 스테이트변수인데
모든 컴포넌트넌트에서 접근할 수 있는 스테이트변수
를 의미한다
👉
redux
,context api
, ....
context
를 만든다.createContext
를 활용하여ex) const ABC = createContext();
- 해당 내가 만든
context provider
를 활용하여 감싸준다(provider로 감싼
자식들에서
는 모두 사용가능)ex) <ABC.Provider> //이안에그려지는 자식 컴포넌트에서는 모두 값 접근가능 </ABC.Provider>
- 이때 자식에서 사용할 수 있도록 하고 싶은 값들은
provider
의value에
다 넘겨준다ex) <ABC.Provider value={ {a:값, b:값, c:값} }> //이안에그려지는 자식 컴포넌트에서는 모두 //{a:값, b:값, c:값} 값 접근가능 </ABC.Provider>
자식 컴포넌트
에서는useContext를
사용하여 어떤context
에서 어떤 값을 사용하고 싶은지 명시한다ex) const value = useContext(ABC); //구조분해할당도 사용가능 const {b, c} = useContext(ABC);
App.js
import { createContext, useState } from 'react';
export const UserContext = createContext(); //다른데서도 써야하니까 export 써주는거 잊지말고
const App = ()=>{
// 전역에서 사용할 변수
const [ accessToken, setAccessToken ] = useState(null); //로그인안한상태로 null 설정
return (
<UserContext.Provider value={ {accessToken, setAccessToken} }>
<RouterProvider router={router}/>
</UserContext.Provider>
);
}
login.js
import { UserContext } from "../../App";
const LoginPage = () => {
// App.js에 있는 accessToken 변수와 setAccessToken 함수 사용하기
const { setAccessToken } = useContext(UserContext);
if (check) {
// 로그인한 회원 조회
try {
let res = await axios.post('/api/login', { email, password });
alert(res.data.accessToken);
// 로컬스토리지에 저장
localStorage.setItem('accessToken', res.data.accessToken);
//전역상태변수(App.js에있는 accessToken)에 저장
setAccessToken(res.data.accessToken);
navigate('/', { replace: false });
} catch (err) {
console.log(err);
if (err.response.status === 404) {
alert('아이디 또는 비밀번호를 확인해주세요');
}
}
}
}