본문 바로가기
파이썬/파이썬 플라스크

ep 13. flask로 instagram Clone 코딩 - 4 (JWT)

by L_SU 2022. 11. 6.

지난 시간엔 백엔드에서 회원가입 API까지 구현을 해봤습니다!

이제 회원가입을 했으니 자연스레 로그인도 세트로 생각이 드실 겁니다.

 

하지만 여기서 우리는 궁금한 점이 있을 것입니다. 

바로 회원가입의 경우 우리가 값을 넣고 그 값이 유효한지만 판별하면 끝이지만

로그인은 그렇지 않습니다.

들어온 값이 맞는지 확인을 해주고, 그 값이 일치하다면 단순히 OK가 아닌 "유지"가 되어야 합니다.

이때 우리가 사용해볼 수 있는 것이 JWT 입니다.

JWT가 무엇인지 간단하게 설명드리겠습니다.

 

JWT란?

먼저 JWT는 JSON Web Token 의 줄임말 입니다.

사전적인 의미로는 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준입니다.

아직까지는 이게 무슨 말인지 이해가 잘 안 될 것이고, 그저 낯설기만 할 것입니다.

이해를 돕기 위해 필자가 간단히 풀어서 설명을 해보겠습니다.

저희는 회원가입을 구현했고, 이는 데이터가 유효하다면 데이터 베이스에 저장되게 만들어져 있습니다.

로그인을 진행할 땐 이 데이터베이스에 있는 값과 저희가 POST 요청을 보낸 값이 일치하는 지 확인을 하는 과정을 걸치게 될 것입니다.자 여기서 아까 말씀드린 문제가 발생합니다. 일치하지 않을 경우를 처리를 하는 것은 간단하지만 일치했을 경우 이를 어떻게 유지 시키냐 입니다. 이때 바로 사용 되는 게 JWT입니다.방법은 간단합니다. 토큰에 데이터베이스에 있는 정보를 저장하고, 이를 데이터가 일치하면 토큰을 발급해주는 것입니다.그렇다면 이 토큰이 유효한 이상 토큰만 계속해서 넘겨준다면 현재 로그인되어 있는 정보를 계속해서 유지 시켜줄 수 있는 것입니다. 여기서 토큰이 얼마나 유효할지, 언제 갱신 되는 지 등 다양한 얘기가 있지만 당장은 JWT라는 것을 이해하는 것만으로도 힘들 수 있기 때문에 "로그인 된 정보를 토큰이라는 것에 저장해 로그인 된 상태를 유지 한다." 라는 느낌의 개념이 JWT를 이용한 로그인 인증 방식이다 라고 어느정도 이해해주시면 좋을 것 같습니다.

 

그렇다면 이제 본격적으로 이 JWT를 구현해봅시다.

 

 

토큰은 로그인할 때 발급 됨으로 resources\user.py 에 다음과 같이 jwt 사용에 필요한 내용을 import 해줍시다.

 

이제 로그인API를 생성해줬습니다 내용을 이해하는데엔 큰 지장이 없다. 생각이 듭니다.

 

그리고 언제나 그렇듯 리소스를 등록해줍니다.

 

그리고 postman으로 확인을 해보면 정상적으로 처리가 되어 토큰이 발급 되는 것을 확인하실 수 있습니다.

 

이 토큰 값을 jwt.io에서 확인해보면

이렇게 decode 해보실 수 있습니다.

 

이제 발급을 해봤으니 이를 활용한 작업들도 구현해봅시다!

 

예를 들어 자신이 쓴 글만 수정이 가능하도록 하는 작업등이 있겠죠?

 

   @jwt.expired_token_loader
    def expired_token_callback(jwt_header, jwt_payload):
        """
        토큰이 만료되었을 때의 에러 메시지를 지정합니다.
        """
        return (
            jsonify({"Error": "토큰이 만료되었습니다."}),
            401,
        )

    @jwt.invalid_token_loader
    def invalid_token_callback(error):
        """
        토큰이 잘못된 값일 때의 에러 메시지를 지정합니다.
        """
        return (
            jsonify({"Error": "잘못된 토큰입니다."}),
            401,
        )

    @jwt.unauthorized_loader
    def missing_token_callback(error):
        """ """
        return (
            jsonify(
                {
                    "Error": "토큰 정보가 필요합니다.",
                }
            ),
            401,
        )

먼저 에러처리를 위해 api/__init__.py 에 다음과 같은 코드를 추가해줍니다.

 

 

그리고 create_app() 아래에 다음과 같은 코드를 추가합니다. 에러 메시지를 한글로 출력해주기 위함입니다.

 

이제 메서드에 대한 처리를 해보겠습니다.

수정할 파일은 resources/post.py 입니다.

먼저 get 메서드 입니다. get 같은 경우는 조회임으로 이는 토큰이 없는 사람들도 조회할 수 있게 그대로 두었습니다.

 

 

다음으로 post인데 저 jwt_required가 있어야 토큰이 있는자만 접근할 수 있도록 설정을 해주기 때문에 import 해줍니다.

 

 

그 후 이렇게 코드를 추가해주시면 되겠습니다.

 

게시물 작성(post) 외에도 수정과 삭제 역시 권한이 있는 사람만이 할 수 있도록 설정해야 됩니다.

그렇기에 다음과 같이 코드를 추가해줍니다.

로그인 후 발급받은 토큰을 다음과 같이 넣어주시고, 글을 작성해봅시다.

그럼 이렇게 잘 작성되는 것을 확인해보실 수 있습니다!

 

이제 토큰을 활용해 권한이 있는자만 접근할 수 있도록 코드를 짜봤습니다..

하지만 위에 토큰에 대해 설명할 때 언급했듯이 이 토큰은 유효기간이라는 것이 존재합니다.

그 이유는 토큰이 무한정 유효하다면 보안에 대한 문제가 크다고 말씀드릴 수 있습니다.

그렇기에 이 유효기간이 지나버리면 access token을 더이상 사용하지 못하게 되는데 이때 엑서스 토큰을 다시 발급받기 위해 사용해주는 것이 바로 refresh token 입니다.

 

 

먼저 models\user.py 파일에 다음과 같이 refresh_token을 저장하기 위한 테이블을 하나 생성해줍니다.

데이터베이스에 변경사항이 생겼음으로 flask db migrate, flask db upgrade로 업데이트 시켜줍니다.

 

테이블이 정상적으로 생성된 것을 확인해봅시다.

 

그리고 다음과 같이 로그인을 수정해주면 되는데.. 위에서부터 살펴보면 username에 맞는 refresh_token이 존재할 경우 업데이트, 그렇지 않으면 저장한다. 라는 if~else문이라 생각하면 되겠다.

 

models\user.py 의 RefreshTokenModel에  다음과 같은 코드를 추가해줍니다.

try 문인데 리프레시 토큰이 존재하면 그 토큰의 user를 가져오고, 존재하지 않을 경우 None을 리턴하는 코드이다.

 

class RefreshToken(MethodView):


    @jwt_required(refresh=True)
    def post(self):

        identity = get_jwt_identity()
        token = dict(request.headers)["Authorization"][7:]
        user = RefreshTokenModel.get_user_by_token(token)
        if not user:
            return {"Unauthorized": "Refresh Token은 2회 이상 사용될 수 없습니다."}, 401
        # access token, refresh token 발급
        access_token = create_access_token(fresh=True, identity=identity)
        refresh_token = create_refresh_token(identity=user.username)
        if user:
            token = user.token[0]
            token.refresh_token_value = refresh_token
            token.save_to_db()
            return {"access_token": access_token, "refresh_token": refresh_token}, 200

 

위의 코드는 resources\user.py에 추가한 코드이다.

RefreshToken 을 받아 검증하고, 새로운 RefreshToken,  AccessToken 을 발급합니다.
여기서 RefreshToken 은 일회용이므로, 새로운 RefreshToken 이 발급되면 데이터베이스에 그 값을 저장해줍니다.

 

api\__init__.py 에 토큰의 유효기간을 지정해줍니다.

 

그리고 이렇게 리소스를 등록해주면 끝이 납니다..!

 

간단하게 코드가 잘 구현되었는지 확인해봅시다.

 

먼저 로그인을 시도해봅니다.

 

발급받은 refresh_token을 이용해 주소/refresh/ 에서 새로 토큰을 발급 받아봅니다.

 

위에서 아무런 것도 바꾸지 않은 상태로 send를 눌러보면 다음과 같은 문장과 함께 발급되지 않아야 합니다. 마지막으로 refresh에서 받은 refreshtoken 값으로 바꿔 넣어 다시 send를 눌렀을 때 새로 받을 수 있으면, 코드를 정상적이게 짠 것입니다!

 

 

이제 드디어 이번 시간의 마지막,

게시물 작성시 저희가 직접 id를 넣는 것이 아닌 작성자가 알아서 들어가게끔 바꿔보도록 하겠습니다.

 

게시물 작성 - 글쓴이 자동 추가 구현

 

먼저 Schemas\post.py 파일을 다음과 같이 수정합니다.

제목, 내용만 받을 수 있게 된 것입니다.

 

다음으로 resources\post.py를 수정합니다. 클라이언트가  JWT를 우리에게 보내줘서 거기 있는 유저의 정보를 받아 쓰는 것입니다.

 

정상적으로 코드를 작성했다면 위와 같이 token을 넣고 제목과 내용만 넣어도 알아서 작성자가 들어가게 됩니다.

 

본인만 게시물 수정, author_id 없이 가능한 API 구현

 

models\post.py 에 다음과 같은 함수 추가

반복문이 도는 동안 setattr() 메서드를 사용해 "해당 게시물의 () 라는 속성을 ()로 설정합니다" 의 로직을 사용합니다.

 

resource\post.py 에서 put 메서드 코드를 수정한 내용입니다. 먼저 게시물 존재 여부를 체크하고, 저자와 요청이 보낸 사람이 같으면 수정할 수 있게 됩니다.

 

이 역시 postman으로 확인해줍니다.

 

감사합니다아...!!!