[Django] 소셜로그인 구현하기 (네이버)

유승인·2025년 2월 19일
0
post-thumbnail

저는 작년에 프로젝트를 진행할때 소셜로그인을 리액트를 이용하여 프론트로만 구현해봤었습니다. 그때 당시에도 저희 팀의 백엔드 팀원들은 장고를 사용했었는데, 그때 실제로 소셜로그인이 구현되는것을 보고 신기해했던 기억이 있어서 저도 기회가 된다면 서버를 통한 소셜로그인을 직접 제가 해볼수 있다면 좋겠다는 생각을 항상 하곤 했었는데, 이번에 피로그래밍 동아리 팀플에서 우연히 기회가 돼서 소셜로그인을 구현해볼 기회가 있었습니다.
장고를 통해 네이버,카카오 소셜로그인을 구현하였고 이 소셜로그인은 후에 있을 피로그래밍 최종 프로젝트에서도 분명히 사용할 예정이기에 구현방법을 정리해놓으면 좋겠다고 생각하여 글을 작성합니다. (사실 소셜 로그인 자체가 하나를 구현할줄 안다면 방식은 거의 동일하기 때문에 네이버를 대표 예시로 두고 글을 진행합니다.)
이번 소셜로그인은 프론트의 리액트, 뷰 등을 사용하지않고 장고만을 사용해서 모든 소셜로그인을 구현했습니다.

🔑 애플리케이션 등록, 키 발급

우선 네이버 소셜 로그인을 하려면 Naver Developer에서 애플리케이션을 등록해야합니다.
네이버 디펠로퍼 상단 메뉴인 내 애플리케이션을 들어가서 Application 등록을 누르게 되면

사진과 같이 애플리케이션 이름과 네이버의 어떤 api를 사용할것인지 선택할수 있습니다.
애플리케이션 이름은 각자 프로젝트 이름에 맞게 적절히 설정하고 사용 api는 네이버 로그인을 선택합니다.

그 이후로는

사용자 식별을 위해 필수적으로 설정해야할 식별자들을 선택해주고 네이버 로그인을 사용하는 서비스 url, 리디렉션할 Callback URL을 설정해줍니다. (현재는 로컬에서만 사용하기 때문에 사진처럼 설정했지만 배포를 진행하면 실제 도메인 url를 넣어야합니다.)
이렇게 모든 설정을 완료하면 네이버는 저희에게 로그인 api를 사용할 수 있는 Client ID, Client Secret를 발급해주기 때문에 여기까지 하면 우선 네이버에서 설정해야하는것은 완료!
여기서 주의할점은 네이버 로그인 API의 콜백 URL은 OAuth 인증 과정에서 중요하기 때문에 네이버 로그인 페이지에서 사용자 인증 후, 설정된 콜백 URL로 리디렉트하려고 시도하지만 이 url이 달라진다면 네이버 로그인이 안돼서 코드에서 설정해주는 리디렉트 url과 동일해야합니다! (저도 계속 코드를 수정하다가 이 부분이 잘 되지않아서 약간의 시간을 소요했었습니다.)
그리고 이렇게 등록을 완료하고 발급받은 Client ID, Client Secret은 흔히 말하는 API Key 이기 때문에 이것은 보안상 노출되게 된다면 문제가 생길수 있는 키들입니다.
이런 API Key들을 노출되지 않게 적용하는 방법은 여러가지가 있지만 이번에 저는 프로젝트 가장 루트 디렉토리에 secrets.json 파일을 따로 만들어줬고

{   
	"NAVER_CLIENT_ID": "*******************",
  	"NAVER_CLIENT_SECRET": "***********",
}

따로 키 값들을 변수로 설정해주어 .gitignore에 secrets.json 파일을 추가해주었습니다!

네이버 API Key 발급 과정

  1. 내 애플리케이션을 들어가서 Application 등록
  2. 애플리케이션 이름, 사용 API (네이버 로그인) 설정
  3. 식별자 설정 및 Callback URL 설정
  4. 발급받은 Client ID, Client Secret을 secrets.json에 넣어주기
  5. .gitignore에 secrets.json 등록

💡 장고에서 코드를 통해 네이버 로그인 구현하기

이제 이렇게 실제로 발급받은 api key를 통해 실제로 프로젝트에서도 소셜 로그인이 가능하도록 해보겠습니다!
우선 저희가 secrets.json에 넣어놓은 키를 사용하기 위해 settings.py에 Client ID, Client Secret을 설정해줍니다.

NAVER_CLIENT_ID = get_secret("NAVER_CLIENT_ID")
NAVER_CLIENT_SECRET = get_secret("NAVER_CLIENT_SECRET")

이런식으로 키 자체의 값을 넣는게 아니라 저희가 secrets.json에 설정해준 변수 이름으로 넣어주면 됩니다!

이렇게 secrets.json에 발급받은 Client ID, Client Secret을 실제 프로젝트에 적용하기 위해 settings.py에 등록을 해주려고 합니다.

with open('secrets.json') as f:
    secrets = json.loads(f.read())

def get_secret(setting, secrets=secrets):
    try:
        return secrets[setting]
    except KeyError:
        error_msg = f'Set the {setting} environment variable'
        raise ImproperlyConfigured(error_msg)
        
NAVER_CLIENT_ID = get_secret("NAVER_CLIENT_ID")
NAVER_CLIENT_SECRET = get_secret("NAVER_CLIENT_SECRET")

저는 이렇게 settings.py에서 네이버 소셜로그인을 이용하기 위해 간단하게 구현을 하였는데, 간단하게 코드를 설명하자면

with open('secrets.json') as f:
    secrets = json.loads(f.read())

은 말그대로 secrets.json 파일을 읽어서 JSON 데이터로 변환시킨 후, json.loads(f.read())을 통해 해당 파일 내용을 문자열에서 딕셔너리 형태로 변환시킵니다.
이렇게 변환 시킨 내용들을 get_secret 함수를 통해 try-except 문에서 키가 존재하는지 확인하고, 키가 존재한다면 secrets.json에서 읽어온 데이터를 사용하고 없다면 ImproperlyConfigured 예외를 발생시켜 설정 오류가 발생했음을 알립니다.

또한 get_secret을 통해 가져온 Client ID, Client Secret를 환경 변수 설정을 시켜주어 NAVER_CLIENT_ID와 NAVER_CLIENT_SECRET을 통해 해당 값들을 사용할 수 있게 해줬습니다.

그렇다면 네이버 소셜로그인은 어떤 방식으로 동작할까요?
네이버 소셜로그인은 OAuth 2.0을 기반으로 동작합니다. OAuth 2.0은 사용자의 자격 증명을 보호하며 제3자 서비스에서 로그인할 수 있도록 지원하는 인증 방식입니다.
이렇게 OAuth 2.0 방식으로 동작하는 네이버 소셜로그인의 흐름은

1️⃣ 사용자가 네이버 로그인 버튼을 클릭
2️⃣ 네이버 로그인 페이지로 이동 → 사용자 로그인
3️⃣ 네이버에서 인증 코드를 Django 서버로 전달
4️⃣ Django가 네이버 API를 호출해 Access Token 발급
5️⃣ Access Token을 사용해 사용자 프로필 정보 가져오기
6️⃣ 해당 정보를 바탕으로 Django 사용자(User) 모델과 연결하여 로그인 처리
으로 동작하기때문에 저희가 이러한 로그인을 구현하려면 views.py에서 네이버 로그인 페이지로 사용자를 리디렉트하는 함수와 네이버에서 전달한 인증 코드를 사용하여 Access Token을 요청하고, 해당 토큰을 이용해 사용자 프로필 정보를 가져와 Django 사용자 모델과 연동하여 로그인 또는 회원가입을 처리하는 역할을 하는 네이버 로그인 콜백 함수가 필요합니다.

네이버 로그인 url로 이동하는 naver_login 함수

def naver_login(request):
    client_id = settings.NAVER_CLIENT_ID  
    redirect_uri = "http://127.0.0.1:8000/users/login/naver/callback/"
    state = "RANDOM_STATE"  

    return redirect(
        f"https://nid.naver.com/oauth2.0/authorize?response_type=code"
        f"&client_id={client_id}"
        f"&redirect_uri={redirect_uri}"
        f"&state={state}"
    )

이 함수는 네이버 개발자 센터에서 발급받은 client_id와 네이버 로그인 후, 인증 코드를 받을 콜백 URL을 설정하여 해당 변수를 통해 return redirect 부분에서 네이버 로그인 페이지로 사용자를 이동시키는 함수입니다.
(state = "RANDOM_STATE"는 CSRF 공격을 방지하기 위한 랜덤 문자열로 보안을 높히기 위해 사용)
=> 여기서 redirect_uri은 아까 네이버 디벨로퍼에서 제가 직접 설정해준 네이버 로그인 Callback URL을 사용합니다.
이 Callback URL이 달라진다면 소셜로그인 구현이 불가능합니다.
저는 맨 뒤에 / 하나를 더 붙여서 에러가 발생한적도 있으니 완전 똑같게 url을 맞춰줘야 합니다!

Access Token을 요청하고, 네이버에서 사용자 프로필 정보를 가져오는 과정을 진행하는 naver_login_callback 함수

def naver_login_callback(request):
    client_id = settings.NAVER_CLIENT_ID
    client_secret = settings.NAVER_CLIENT_SECRET
    redirect_uri = "http://127.0.0.1:8000/users/login/naver/callback/"
    code = request.GET.get("code")  # 네이버에서 전달한 인증 코드
    state = request.GET.get("state")  # 전달된 state 값

    # Access Token 요청
    token_request = requests.post(
        "https://nid.naver.com/oauth2.0/token",
        data={
            "grant_type": "authorization_code",
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": redirect_uri,
            "code": code,
            "state": state,
        },
    )
    token_json = token_request.json()  # JSON 형식으로 응답 

    if "error" in token_json:  # 오류 발생 시 홈으로 리디렉트
        return redirect("/")

    access_token = token_json.get("access_token")  # Access Token 가져오기

    # Access Token을 사용해 사용자 정보 요청
    profile_request = requests.get(
        "https://openapi.naver.com/v1/nid/me",
        headers={"Authorization": f"Bearer {access_token}"},
    )
    profile_json = profile_request.json()  # JSON 데이터 변환

    if profile_json.get("resultcode") != "00":  # API 응답이 정상인지 확인
        return redirect("/")

    # 사용자 정보 저장 또는 로그인 처리
    response = profile_json.get("response")  # 사용자 정보 추출
    email = response.get("email")
    name = response.get("name")
    profile_image_url = response.get("profile_image")
    birth = response.get("birthday")
    birth_year = response.get("birthyear")
    phone_number = response.get("mobile")
    gender_map = {"M": "M", "F": "F"}  # 성별 매핑
    user_gender = gender_map.get(response.get("gender"), "U")

    
    full_birth = None
    if birth and birth_year:
        full_birth = f"{birth_year}-{birth[:2]}-{birth[3:]}"  

    # DB에서 기존 유저 확인 또는 신규 가입 처리
    try:
        user = User.objects.get(email=email)  # 이메일로 기존 유저 확인
    except User.DoesNotExist:
        # 신규 가입
        user = User.objects.create_user(
            email=email,
            social_id=f"naver_{response.get('id')}",
            username=name,
            nickname=name,
            birth=full_birth,
            user_gender=user_gender,
            user_phone=phone_number
        )
        if profile_image_url:
            # 프로필 이미지 다운로드 후 저장
            response = requests.get(profile_image_url)
            if response.status_code == 200:
                image_name = f"profile_images/{user.email.replace('@', '_')}.jpg"
                user.profile_image.save(image_name, ContentFile(response.content))

        user.save()

    # 계정 정지 확인 및 로그인 처리
    if not user.is_active:
        return render(request, 'mypage/account_suspended.html', {'error': '계정 이용이 정지되었습니다.'})

    login(request, user)  # Django 로그인 처리
    return redirect('main:home')  # 메인 페이지로 이동

네이버 로그인 콜백 과정을 설명하자면 우선 네이버에서 전달한 code와 state 값을 사용해 Access Token을 요청합니다. 이 토큰을 이용하면 네이버 API를 통해 사용자 정보를 가져올 수 있게됩니다. 그 이후로는 Access Token을 이용해 네이버에서 response 객체를 반환하면 사용자 정보를 쉽게 추출할 수 있습니다.
또한 저는 현재 사용자 추출 정보를 이메일, 이름, 프로필 이미지, 생년월일, 성별, 휴대폰 번호 등을 가져와달라고 요청했기에 해당 정보들을 저장하여 로그인 처리를 시도하며 try except 문을 통해 해당 이메일을 가진 사용자가 있는지 확인합니다. 만약 해당 이메일을 가진 사람이 없다면 신규 가입을 하도록 하고, 또한 프로필 이미지가 있다면 해당 프로필을 다운로드 후 저장할 수 있습니다. 마지막으로 계정 정지 확인을 통해 계정이 비활성화된 경우 로그인 불가를 선언하고, login(request, user)를 호출하여 Django 세션에 로그인 처리를 하여 로그인 완료 후 메인 페이지로 이동합니다.

🤩 결론

저는 제가 직접 네이버 소셜로그인을 구현하기 전에는 대부분 모든 사이트에 존재하는 소셜 로그인의 존재들이 신기했고, 이걸 직접 구현하다는것은 되게 어려울것 이라는 생각을 하였는데, 워낙 네이버 카카오 등에서 로그인 자체의 API를 제공해줘서 적은 코드만으로도 원하는 서비스에 소셜로그인을 가져다 쓸 수 있고, 이 동작 행위는 대부분의 모든 소셜로그인에서 비슷하기 때문에 코드를 이해하고 있으면 나중에 구현할때도 콜백 URL 정도만 바꿔준다면 쉽게 적용할 수 있을것이라고 생각합니다. 요즘 대부분의 프로젝트에서 소셜로그인을 구현해놓는 경우는 필수라고 생각 될 정도로 많이 사용하고 있으니 동작 원리와 기본적인 코드 이해를 바탕으로 이것을 유용하게 사용할 수 있을것이라고 생각하여 즐겁게 구현했습니다.
이번에 직접 해보면서 느낀건 나중에 소셜로그인 기능을 사용 할 때는 문제없이 구현할 수 있을 것 이라는 확신이 생겼고, 다음에 프로젝트를 할 때도 필수적으로 구현할 기능이라고 생각했습니다.
장고를 공부하는 다른 분들도 이러한 소셜로그인을 통해 프로젝트에서 사용자들이 간단하게 로그인을 할 수 있도록 한번 시도해보면 좋을것 같습니다!

profile
한입 개발자

0개의 댓글

관련 채용 정보