카테고리 없음

2024. 6. 18. (화) 슈퍼코딩 부트캠프 신입연수원 Day 14 (중간보고)

태영9922 2024. 6. 19. 00:11

  • 배운 내용 요약 정리
     : 강의 수강 후 배운 내용을 나만의 방식으로 정리

1. JWT란?

- JSON Web Token의 약자로, 유저 인증과 관련된 기술.
- Base64로 인코딩 되어 있음
- JWT는 사용자 정보를 토큰에 포함시킴.(세션은 사용자 정보를 서버측에서 DB를 통해 관리)
- 특정 시간이 지나면 refresh token 발급으로 보안성 향상

1-1 Refresh Token

1) Access Token의 유효기간은 짧다. (ex. MS:60days, Amazon:1hour)
2) Refresh Token의 유효기간은 길다. (ex. MS : 1year)
3) 평소에 API 통신할 때는 Access Token을 사용하고, Refresh Token은 Access Token이 만료되어 갱신될 때만 사용한다.
즉, 통신과정에서 탈취당할 위험이 큰 Access Token의 만료 기간을 짧게 두고 Refresh Token으로 주기적으로 재발급함으로써 피해을 최소화한 것이다.


출처 : https://velog.io/@chuu1019/Access-Token%EA%B3%BC-Refresh-Token%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C

그림1 출처 :  https://velog.io/@chuu1019/Access-Token%EA%B3%BC-Refresh-Token%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C

그림2 출처 : https://velog.io/@chchaeun/%EC%9D%B8%EC%A6%9D%EA%B3%BC

 

 

1-2 Detail Logic

1. 로그인 인증에 성공한 클라이언트는 Refresh Token과 Access Token 두 개를 서버로부터 받는다.
2. 클라이언트는 Refresh Token과 Access Token을 로컬에 저장해놓는다.
3. 클라이언트는 헤더에 Access Token을 넣고 API 통신을 한다. (Authorization)
4. 일정 기간이 지나 Access Token의 유효기간이 만료되었다.
   4.1. Access Token은 이제 유효하지 않으므로 권한이 없는 사용자가 된다.
   4.2. 클라이언트로부터 유효기간이 지난 Access Token을 받은 서버는 401 (Unauthorized) 에러 코드로 응답한다.
   4.3. 401를 통해 클라이언트는 invalid_token (유효기간이 만료되었음)을 알 수 있다.
5. 헤더에 Access Token 대신 Refresh Token을 넣어 API를 재요청한다.
6. Refresh Token으로 사용자의 권한을 확인한 서버는 응답쿼리 헤더에 새로운 Access Token을 넣어 응답한다.
7. 만약 Refresh Token도 만료되었다면 서버는 동일하게 401 error code를 보내고, 클라이언트는 재로그인해야한다.

1-3 결론

- Refresh Token은 JWT 토큰의 탈취 위험을 최소화하고, 사용자 경험을 높이기 위해(빈번한 재로그인 방지를 위해) 나온 것이다.
- Access Token의 유효 기간은 짧고, 평소 API 통신할 때 사용한다.
- Refresh Token의 유효 기간은 길고, Access Token 재발급 받을 때 사용한다.
- Refresh Token은 여전히 탈취 위험이 있고, Refresh Token Rotation을 통해 위험을 줄일 수 있다.

1-4 추가내용

1. Refresh Token 발급: 사용자가 처음 인증에 성공했을 때, Access Token과 함께 Refresh Token도 발급해야 합니다. Refresh Token은 보통 긴 만료 시간을 가지며, Access Token을 새로 발급받는 데 사용됩니다.

2. Refresh Token 저장: 발급된 Refresh Token을 사용자와 연결시키기 위해 저장해야 합니다. 이를 위해 데이터베이스에 Refresh Token과 사용자 ID를 저장하는 테이블을 생성하고, 발급된 Refresh Token을 해당 테이블에 저장합니다.

3. Access Token 재발급 엔드포인트 생성: 클라이언트가 Access Token이 만료되었을 때, 이를 새로 발급받을 수 있는 엔드포인트를 생성해야 합니다. 클라이언트는 이 엔드포인트에 Refresh Token을 전달하고, 서버는 Refresh Token이 유효한지 확인한 후 새로운 Access Token을 발급합니다.

4. Refresh Token 검증: Access Token 재발급 요청을 받았을 때, 전달된 Refresh Token이 유효한지 확인해야 합니다. 이를 위해 데이터베이스에서 해당 Refresh Token을 찾고, 해당 Refresh Token이 만료되지 않았는지 확인합니다. 유효한 경우에만 새로운 Access Token을 발급해야 합니다.

5. 로그아웃 로직 변경: 사용자가 로그아웃 요청을 보낼 때, 발급된 Refresh Token도 함께 폐기해야 합니다. 이를 위해 로그아웃 요청을 받으면, 데이터베이스에서 해당 사용자의 Refresh Token을 삭제하거나 만료시키는 로직을 추가합니다.

 

 

2. 회원가입 창에서 받아온 정보를 DB에 삽입

# 회원가입
@app.post("/signup")
def signup(
    id: Annotated[str, Form()],
    password: Annotated[str, Form()],
    name: Annotated[str, Form()],
    email: Annotated[str, Form()],
):
    try: #중복처리 구문
        cur.execute(
            f"""
                    INSERT INTO users(`id`,`name`,`email`,`password`)
                    VALUES('{id}','{name}','{email}','{password}')
                    """
        )
        con.commit()
        return "200" #성공하면 200 return
    except sqlite3.IntegrityError as e: #중복오류 발생하면 
        return "duplicated id"          #duplicated id return 하여 프론트엔드에서 아이디 중복 표시

 

3. redirecting 구문

window.location.pathname='/login.html'

 

4. 로그인 기능 구현

from fastapi_login import LoginManager

SECRET = "비밀키"
manager = LoginManager(SECRET, "/login")

@manager.user_loader()
def query_user(id):  # 입력한 id가 DB에 존재하는지 확인
    con.row_factory = sqlite3.Row  # 컬럼명도 같이 가져오는 문법
    cur = con.cursor()
    user = cur.execute(
        f"""
                       SELECT * FROM users WHERE id='{id}'
                       """
    ).fetchone()  # id에 해당하는 row한개 전체를 가져옴
    return user


@app.post("/login")
def login(id: Annotated[str, Form()], password: Annotated[str, Form()]):
    user = query_user(id)
    if not user:  # 해당하는 유저가 없을때
        raise InvalidCredentialsException  # error 메세지 출력
    elif (
        password != user["password"]
    ):  # 비밀번호가 틀릴때 user row에서 password column에 해당하는 값을 가져와서 비교
        # query_user 함수에서 row_factory 사용했기때문에 가능
        raise InvalidCredentialsException  # error 메세지 출력
        
         access_token = manager.create_access_token
         (data={"id": user["id"], "name": user["name"], "email": user["email"]})
         #토큰을 만들고 안에 내용을 담아 프론트엔드로 보낸다  

    return "200"
const handleSubmit = async (event) => {
  event.preventDefault();

  const formData = new FormData(form); //입력 form에서 받아온 내용을 FormData객체로 받아옴
  const sha256Password = sha256(formData.get("password")); //받아온 password 해시암호화
  formData.set("password", sha256Password); //formData에 있는 패스워드를 암호화된 패스워드로 다시 값 설정

  const res = await fetch("/login", {
    method: "POST",
    body: formData,
  });

  const data = await res.json();
  if (res.status === 200) {
    alert("로그인에 성공");
    div.innerText = "로그인 성공";
    div.style.color = "blue";

    form.appendChild(div);
    console.log(data);
  } else if (res.status === 401) {
    alert("아이디 패스워드");

    div.innerText = "ID 또는 비밀번호가 일치하지 않습니다";
    div.style.color = "red";

    form.appendChild(div);
  }
};
form.addEventListener("submit", handleSubmit);

 

4-1 왜 저장되어 있는 정보를 확인만 하는데 login 에서 GET을 안쓰고 POST를 쓸까?

< GET >
- URL을 통해 모든 데이터를 전달하기 때문에 전달되는 값이 모두 URL에 노출된다.
- URL 길이 제한 때문에 전송 할 수 있는 데이터의 양이 제한된다.
- '전송 데이터의 형식을 맞추기 위해 인코딩이 필요할 수 있다.
< POST >
- HTTP BODY에 전송데이터를 담아서 전달하기 때문에 데이터 노출이 없다. (암호화는 해야함. 중간에 채갈 수 있다.)
- 전송 데이터의 길이 제한이 없다.

출처 : https://normal-gom-jelly.tistory.com/entry/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%A0-%EB%95%8C-%EC%99%9C-POST%EB%A5%BC-%EC%93%B8%EA%B9%8C%EB%82%9C-GET%EC%93%B8%EB%A0%A4%EA%B3%A0-%ED%96%88%EC%9D%8C

 

4-2 파이선 hashlib 사용한 암호화 test

더보기

- 파이썬 hashlib 사용한 암호화

import hashlib
hashPassword = hashlib.sha256(사용자입력패스워드.encode()).hexdigest()

⬆️ 서버코드

 

signup 할때 password를 같은 방식으로 저장하고 login할때 password를 같은 방식으로 변환해서 비교해야함.

 

 

5. Server Status Code

1. Informational responses (100 – 199)
2. Successful responses (200 – 299)
3. Redirection messages (300 – 399)
4. Client error responses (400 – 499)
5. Server error responses (500 – 599)
출처 : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

 

6. 서버는 Stateless

- HTTP 요청이 처리되면 서버와 클라이언트는 연결을 끊음.
- 서버는 클라이언트가 요청할때 마다 누구인지 모른다.

어떻게 할까?->>>

- HTTP 요청할때 Header에 Access Token을 담아서 요청하기

 

7. AccessToken을 저장시키고 가져오는 방법

- 토큰을 브라우저에 저장하는 방법 : 로컬스토리지 vs 쿠키

1) 쿠키 : 서버로 요청을 보낼때 자동으로 전송되는 작은 데이터파일. HTTP Only 옵션을 통해 XSS, CSRF공격을 방지.
2) 로컬 스토리지 : 브라우저 내부에 있는 저장소. 클라이언트에서 직접 사용 가능. but! 브라우적 닫히면 초기화된다.

7-1 로컬스토리지에 저장하는 코드

const data = await res.json();
const accessToken = data;
window.localStorage.setItem("token", accessToken); //로컬스토리지에 저장, 브라우저를 껐다 켜도 삭제 안됨
window.sessionStorage.setItem("token", accessToken); //세션스토리지에 저장, 브라우저를 껐다 켜면 사라짐

7-2 로컬스토리지에서 가져오는 코드

const accessToken = window.localStorage.getItem("token");
  const res = await fetch("/items", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

 

 

 

 

 

 

 

 

#슈퍼코딩, #1:1관리형부트캠프, #백엔드, #backend, #백엔드공부, #개발공부, #백엔드개발자 #취준일기, #취준기록, #취뽀, #빡공, #HTML/CSS, #javascript, #react , #java, #spring