<To-Do List>
- 64~67강 듣기
- 일일보고 작성하기
<배운 내요 요약 정리>
64강 데이터베이스와 연동해 백엔드 회원 가입 기능 완성하기
1. 회원가입 처리 된 후 로그인 페이지로 옮기기
>> 지난번에 작성했던 signup.js의 handleSubmit 부분에서 회원가입 성공시
- alert를 이용해서 메세지 띄우고
- window.location.pathname="/login.html"; 을 이용해서 페이지 옮기기
if (data === "200") {
div.innerText = "회원가입에 성공했습니다!";
div.style.color = "blue";
alert("회원가입에 성공했습니다.");
window.location.pathname = "/login.html";
}
} else {
div.innerText = "비밀번호가 일치하지 않습니다.";
div.style.color = "red";
}
2. 로그인 프론트엔드 간단히 작성
>> login.html을 생성하고 signup.html과 유사하게 form(input,button)을 이용하여 작성
3. 서버에서 로그인 요청시 기능 구현(JWT와 Status Code 이용)
>> JWT란?(Json Web Token)
- 유저인증과 관련된 기술이다.
- 토큰에다가 Base64로 인코딩을 해서 토큰에 사용자정보를 담아서 전달
- 보통 세션 vs JWT 로 갈림
-> 세션은 사용자 정보를 서버측에서 관리하는 것이고
-> JWT는 사용자 정보를 토큰에 포함시키는 것이다.
=> 결국 이는 확장성 vs 보안성의 문제이다.(세션이 보안성이 좋고 JWT가 확장성이 좋음) but, 보안성은 refresh token으로 높일 수 있어 현업에서는 JWT를 많이 사용함
>> server status code
- 다음의 검색명으로 구글에서 서치하면 더 자세하게 나와서 좋음
>> 로그인에 대해서 백엔드 기능 구현하기
- fastapi_login에서 LoginManager를 이용하여 로그인 기능을 구현할 것임 -> 다음과 같이 작성
SECRET = "super-coding"
manager = LoginManager(SECRET,"/login")
- 로그인 요청을 받으면 id와 password를 입력 받는다.
- query_user(id) 함수를 이용하여 입력받은 id와 같은 id를 가지는 user의 정보를 반환한다.
- 만약 user 정보가 없다면 raise InvalidCredentailsException을 이용하여 status code를 401로 내려준다.
- 만약 user정보는 있지만 저장된 비밀번호와 입력받은 비밀번호가 다르다면 마찬가지로 raise InvalidCredentailsException 을 한다.
- user 정보도 있고 password도 동일하다면 access_token과 함께 status code 200을 내려주게 된다.
@manager.user_loader()
def query_user(data):
WHERE_STATEMENTS = f'id="{data}"'
if type(data) == dict:
WHERE_STATEMENTS = f'''id="{data['id']}"'''
con.row_factory = sqlite3.Row
cur = con.cursor()
user = cur.execute(f"""
SELECT * FROM users WHERE {WHERE_STATEMENTS}
""").fetchone()
return user
@app.post("/login")
def login(id:Annotated[str,Form()],
password:Annotated[str,Form()]):
user = query_user(id)
if not user:
raise InvalidCredentialsException
# 파이썬은 raise로 오류메세지 던짐
# InvalidCredentialsException이 자동으로 status code를 401로 내려줌
elif password != user['password']:
raise InvalidCredentialsException
access_token = manager.create_access_token(data={
'sub':{
'id':user['id'],
'name':user['name'],
'email':user['email']
}
})
return {'access_token' : access_token}
# 뭐로 리턴하든 status code는 200을 내려줌
4. login.js를 생성하고 서버에 요청하기 + 상태코드를 받아 로그인여부 처리하기
>> login.html에서 submit이 발생하면 login.js에서 main.py(서버)로 post 요청을 보냄
const form = document.querySelector("#login-form");
form.addEventListener("submit", handleSubmitForm);
>> 요청한 후 응답이 오면 status code를 통해 로그인의 여부를 처리하기
- 입력받은 데이터에 대해 해시 암호화를 한 후 서버로 보내기
- status code가 200 이면 alert를 통해 로그인 성공 알람 띄우기
- 또한 window.location.pathname="/"을 통해 화면 전환하기
const handleSubmitForm = async (event) => {
event.preventDefault();
const formData = new FormData(form);
const sha256Password = sha256(formData.get("password"));
// 폼데이터의 password내에 있는 데이터를 받아와서 sha256으로 변환
formData.set("password", sha256Password);
// formData 내에서 우리가 signup.html에서 password라고 name 지었던 것에 sha256Password를 설정하기
// const div = document.querySelector("#info");
const res = await fetch("/login", {
method: "post",
body: formData,
});
const data = await res.json();
const accessToken = data.access_token;
console.log(accessToken);
if (res.status === 200) {
alert("로그인에 성공했습니다.");
window.location.pathname = "/";
} else if (res.status === 401) {
alert("아이디 또는 비밀번호가 일치하지 않습니다.");
}
};
65강 어떻게 로그인을 유지시킬까?
>> 서버는 우리를 모른다. (서버는 stateless 속성을 가지기 때문이다)
- http요청이 처리되면 서버와 클라이언트는 연결을 끊는다.
- 따라서 서버는 매 요청시마다 클라이언트가 누군지 알 수 없다.
- 즉 데이터를 요청할때마다 헤더에 엑세스토큰을 담아서 요청해야한다.(body에 넣지 않음)
(* header란 클라이언트에서 서버로 요청을 보낼때 요청의 부가 정보를 담고 있는 데이터이다.)
>> 지난번까지 회원가입, 로그인은 마무리 함 -> 이제 로그인 이후에도 로그인상태를 유지할 수 있도록 해보자
- 우선 mian.py 서버에서 items.html에 대해 get 요청을 받았을때 유저조건을 제한한다.
async def get_items(user=Depends(manager)):
...
- login.js에서 로그인 요청을 보낸 후에 토큰을 응답을 받게 되는데 그 토큰을 저장해두기
...
const res = await fetch("/login", {
method: "post",
body: formData,
});
const data = await res.json();
accessToken = data.access_token;
...
1. 로그인 후에 토큰을 가지지 않고 요청할때
- login.js 에서 로그인에 성공하게 되면 성공멘트를 띄우고 상품 리스트를 볼 수 있는 버튼을 추가한다.
- 그리고 버튼을 누르면 서버에 items로 get 요청을 보내고 데이터를 받아오도록 한다.
const infoDiv = document.querySelector("#info");
infoDiv.innerText = "로그인에 성공했습니다.";
const btn = document.createElement("button");
btn.innerText = "상품 가져오기!";
btn.addEventListener("click", async () => {
const res = await fetch("/items");
const data = await res.json();
console.log(data);
- 이렇게 하면 다음과 같이 401 에러가 뜬다. (http 요청이 실행된 후 서버와 클라이언트는 연결을 끊었기때문에 유저조건에서 탈락되는 것임)
2. 그렇다면 이번에는 헤더에 토큰을 넣어서 요청을 보내기
- 1번과 다 동일하지만 버튼을 눌렀을때 item에 get 요청을 보낼때 토큰을 포함해서 주는 것이다.
const infoDiv = document.querySelector("#info");
infoDiv.innerText = "로그인에 성공했습니다.";
const btn = document.createElement("button");
btn.innerText = "상품 가져오기!";
btn.addEventListener("click", async () => {
const res = await fetch("/items", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await res.json();
console.log(data);
});
- 다음과 같이 데이터의 값이 콘솔에 잘 출력되는 것을 볼 수 있다.
66강 브라우저가 닫혀도 로그인 정보를 유지시키는 방법
>> 브라우저에 데이터를 저장하는 방법을 사용
- 쿠키, 로컬스토리지, 세션스토리지가 있다.
- 쿠기
: 서버로 요청을 보낼 때 자동으로 전송되는 작은 데이터 파일
: 자동이기에 보완이 약할 수 있다고 생각하지만 HttpOnly 옵션을 통해 XSS,CRSF 공격을 방지 가능
- 로컬 스토리지
: 브라우저 내부에 있는 저장소
: 클라이언트 측에서 직접 사용 가능
: 적정시간이 지나면 토큰을 만료시켜 지워줌
- 세션 스토리지
: 로컬 스토리지와 마찬가지로 내부에 있는 저장소
: 브라우저가 닫히면 초기화됨
>> 우리는 로컬 스토리지를 이용할 것임 !
- login.js 에서 로컬스토리지에 토큰을 저장함
- 로그인이 되면 알람을 띄우고 목록화면으로 전환
const data = await res.json();
const accessToken = data.access_token;
window.localStorage.setItem("token", accessToken);
alert("로그인되었습니다!");
window.location.pathname = "/";
- index.js 에서 서버에 items에 대한 get 요청을 보낼때 헤더에 토큰을 담아서 전달
- 그리고 만약 그 토큰이 올바르지 않다면 로그인페이지로 다시 돌려보내기(이 부분은 조금 수정해야할듯... 틀렸는데도 일단 화면 띄우고 다시 로그인으로 돌려보내니 보기 안좋음)
const fetchList = async () => {
const accessToken = window.localStorage.getItem("token");
const res = await fetch("/items", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (res.status === 401) {
alert("로그인이 필요합니다!");
window.location.pathname = "/login.html";
return;
}
const data = await res.json();
renderData(data);
};
>> 로컬 스토리지에서의 만료시기
- 로컬 스토리지에 저장하는 방식은 탈취의 위험이 높으므로 토큰 만료시기를 몇시간 단위로 짧게 해서 보안을 높인다.
- 브라우저에서 검사를 들어간 후 Application을 들어가면 토큰의 Value를 확인할 수 있다.
- 다음의 Value 값을 디코딩하면 exp값이 나온다.(https://jwt.io/ 여기서 할 수 있음)
- exp 값이 만료시간이며, exp - new Date().getTime()/1000을 해주면 만료까지 남은 초수를 알 수 있다.
'슈퍼코딩 부트캠프 > week3' 카테고리의 다른 글
슈퍼코딩 부트캠프 신입연수원 Week3 Day5 일일보고 (0) | 2024.07.20 |
---|---|
2024.07.11(목) 슈퍼코딩 부트캠프 신입연수원 week3 Day4 일일보고 (2) | 2024.07.13 |
2024.07.10(수) 슈퍼코딩 부트캠프 신입연수원 week3 Day3 일일보고 (0) | 2024.07.11 |
2024.07.08(월) 슈퍼코딩 부트캠프 신입연수원 week3 Day1 일일보고 (0) | 2024.07.09 |