개발 노트/개발 삽질

Brunch와 KaKao 인증에 대한 삽질

j4ko 2023. 10. 3. 01:10
728x90
반응형

목차

     

    개요 

    얼마 전 Brunch에 발행된 글을 살펴보다 키워드가 잘못 설정되었음을 깨달았다. 하나의 키워드를  특정 글에 업데이트만 하면 되는 것이기에 일괄적으로 수정하려고 했지만 Brunch는 이러한 기능을 제공하지 않았다. 

     

    이를 코드로 풀어내고자 Brunch 계정이 KaKao와 연계되어 있어 KaKao API에 혹시나 Brunch 게시글을 수정하는 기능이 있나 찾아봤지만 제공되는 기능은 아니었다. 오픈된 API가 없다면 통신 과정을 재연시키면 가능하기에 Brunch 내부에서 통신하는 주소를 관찰하게 되었다. Brunch 내부적으로는 API를 요청하는 주소가 몇 건 들어있었고 이를 활용하고자 했다.

     

    문제는 Brunch에서 사용하는 API 요청을 재연시키려면 KaKao의 OAuth 로그인을 먼저 수행한 뒤에 Brunch API를 사용해 야한 든 점이었다. 여러 시행착오를 겪은 다음 결국 Brunch API 요청을 재연시키는 작업은 실패했지만 이 과정에서 얻은 정보를 기록하고자 한다. 

     

    1. KaKao Oauth 로그인 처리

    제일 처음 진행해야 하는 건 KaKao API를 사용해 카카오 로그인(Oauth)을 하는 것이었다. 이 처리를 먼저 해둬야 나중에 Brunch에서 내 계정에 관련된 정보를 가져오는 Setting을 할 수 있기 때문이다. 카카오 로그인에 관한 가이드는 KaKao developers에서 친절히 설명하고 있다. 아래 이미지는 KaKao developers에서 설명하고 있는 카카오 로그인 과정이다.

    시퀀스 다이어그램을 보니 복잡할 건 없어 보인다. 관련 문서에서는 curl을 이용한 예시를 보여주고 있지만 로그인 이후에도 추후에 필요한 동작을 구성해야 했기에 Python으로 사용했다. 시퀀스 다이어그램에 나타난 과정에 따라 requests를 써서 구현해도 되지만 필자는 authlib이라는 라이브러리를 통해 처리했다.

     

    1.1 Authlib을 이용한 카카오 로그인 요청 코드 구현

    다음은 Authlib을 이용한 카카오 로그인 요청을 처리하는 코드이다.

    from authlib.integrations.requests_client import OAuth2Session
    
    client = OAuth2Session(client_id="YOUR_CLIENT_ID")
    uri, state = client.create_authorization_url(
        "https://kauth.kakao.com/oauth/authorize",
        through_account="true",
        redirect_uri="http://localhost:8080"
    )

    위 코드는 카카오 로그인 시퀀스 다이어그램에서 1단계에 해당하는 과정을 처리하는 코드이다. 위 코드를 실행하면 다음과 같은 결과를 뽑을 수 있다.

     

    첨부한 코드의 uri와 state를 출력한 결과이다. 문제는 uri는 authorization_code를 받기 위한 uri라는 점이다. OAuth 인증에서는 Resource Server로부터 authorization_code를 발급받기 위해 사용자의 동의를 받어야 하는 브라우저가 있어야 하 한다. 그런데 위의 코드만 가지고는 이러한 처리를 하진 못한다.

     

    Python을 통해 OAuth 인증을 구현하는 예제를 보면 Django나 Flask와 같은 프레임워크를 통해 설명되곤 하는데 굳이 FrameWork를 사용해서 KaKao 로그인을 처리하기엔 배보다 배꼽이 더 큰 상황이다.

     

    1.2 Python HTTPServer를 이용해 authorization_code 가져오기

    프레임워크에 도움 없이 authorization_code를 발급받기 위한 방법으로 Python에서 builtin 되어있는 http 모듈을 가져다 사용하기로 했다. Python3을 사용하고 있다면 다음과 같은 명령을 사용할 수 있다.

    $ python -m http.server 8080

    이는 python에 내장된 http server를 실행하는 명령인데 python 내부적으로는 이에 관련된 객체가 제공된다. 더 나아가 local에 HTTPServer를 직접 만들어 사용한다면 HTTPServer에 들어오는 요청을 컨트롤할 수 있게 된다. 이는 OAuth 리디렉션에 따라 어느 코드를 사용하게 만들지 결정하게 만드는 하나의 아이디어였다. builtin 되어있는 HTTPServer를 상황에 맞게 사용하기 위해 다음과 같은 형태의 코드가 필요했다.

    from http.server import HTTPServer, BaseHTTPRequestHandler
    
    class MyHandler(BaseHTTPRequestHandler):
        def do_GET(self): ...
    
    if __name__ == '__main__':
        server = HTTPServer(('', 8080), MyHandler)
        server.serve_forever()

    HTTPServer의 Request에 따라 핸들링처리만 수행하면 되었기에 필요한 코드는 굉장히 간단했다. 이를 기반으로 다음과 같은 동작을 구상했다.

    구상한 그림을 다음과 같은 코드로 구현했다.

    from http.server import HTTPServer, BaseHTTPRequestHandler
    
    from authlib.integrations.requests_client import OAuth2Session
    
    
    class MyHandler(BaseHTTPRequestHandler):
        client = OAuth2Session(client_id="YOUR_CLIENT_ID")
    
        def get_kako_authrization_code(self):
            uri, state = self.client.create_authorization_url(
                "https://kauth.kakao.com/oauth/authorize",
                through_account="true",
                redirect_uri="http://localhost:8080"
            )
            self.send_response(302)
            self.send_header("Location", uri)
            self.end_headers()
    
        def get_kakao_authorization_token(self):
            token = self.client.fetch_token(
                url="https://kauth.kakao.com/oauth/token",
                authorization_response=self.path
            )
            print(token)
    
        def do_GET(self):
            if "code" not in self.path:
                self.get_kako_authrization_code()
            else:
                self.get_kakao_authorization_token()
                exit()
    
    
    if __name__ == '__main__':
        server = HTTPServer(('', 8080), MyHandler)
        server.serve_forever()

    이 코드를 동작시키면 다음과 같은 결과를 얻을 수 있다.

    이로써 KaKao Login은 완료했다.

     

    2. Cookie를 이용하는 건가?

    이제 이 token 정보를 어떤 방식으로 사용해야 할지 고민해야 할 때이다. Brunch에 로그인한 상태로 브라우저에서 "애플리케이션" 탭을 보다가 Cookie에 다음과 같은 정보가 있는 걸 확인했다.

    다른 쿠키 이름들은 별 의미가 없어 보였는데 bid라는 쿠키가 눈에 띄었다. 저 bid라는 게 무엇을 뜻하는지는 몰랐지만 값을 지워버리니 로그인이 풀려버리는 걸 확인했다. 이제 문제는 "bid"에 들어있는 값이 무엇이며 언제 생성되는지를 관찰할 차례였다. 제일 확실한 방법은 Web Proxy를 걸고 Request & Response를 하나하나 까보는 것이다. 그런데 관찰하던 중 특이한 점을 하나 알게 되었다.

     

    Brunch 사이트에서 KaKao 로그인하기 버튼을 눌렀을 때 나가는 요청인데 client_id가 Response에 포함되었다. 이로부터 추측할 수 있는 건 "KaKao 로그인 API"을 사용하기 위해 미리 생성해 두었던 APP의 clientid를 사용했던 것처럼 brunch에도 고유 clientid가 있는 게 아닐까라는 점이다. 이는 앞서 구현한 KaKao Login API 코드에 Brunch를 연결할 수 있는 포인트가 되려나 싶었다. 이후 통신과정을 계속 보던 찰나 인증 단계가 끝날 때쯤 Response에 Set-Cookie 헤더를 통해 "bid"가 세팅된다는 것을 알게 되었다.

     

     

    3. Brunch를 Oauth 통신 재연의 한계

    지금까지 파악한 내용대로라면 Brunch가 KaKao에 로그인 요청을 보내는 과정만 코드로 옮겨서 Local HTTPServer에 태우면 Local HTTPServer의 로그를 통해서라도 통신 관련 정보를 얻을 수 있을 것 같다. 하지만 아쉽게도 redirection_url 설정을 제어하지 못했다. KaKao OAuth 인증은 redirection_url이 등록되어 있는 주소와 일치하지 않으면 다음과 같은 에러가 표시된다.

     

    4. 그 외의 한계점

    Web Proxy를 통해 관찰한 또 하나의 특징은 Brunch는 자체적으로 API를 사용한다는 점이다.

    공개된 API가 아니어서 그런지 파악하는데 무엇을 하는 API인지 파악하는 것에도 꽤나 큰 공수를 들여야 할 것 같다는 점에서 Brunch API를 재연시켜 키워드를 일괄 수정하는 작업은 포기하기로 결정했다.

     

     

    마치며

    비록 결과는 명확하게 남지 않은 삽질이었지만 Authlib과 Local HTTPServer를 사용하여 Oauth를 수행하는 코드를 작성하는 방법을 알았기에 마냥 헛된 삽질은 아니었단 평을 스스로에 내린다.

    728x90
    반응형