본문 바로가기

Programming/Django

[02. Django-allauth] 006. Custom 로그인

728x90

구현한 로그인 화면을 통해 로그인을 하면 Home화면으로 리다이렉팅 된다. 로그인 하기 직전 페이지로 돌아오지 않는다. 그리고 로그인 할 때 마다 이전 글 [그림 2.5]와 같은 로그인 화면에서 매번 로그인해야 해서 번거롭다.

 

로그인 하는 코드를 조금 수정하면 이런 불편함을 해소할 수 있다.

 

url.py

urlpatterns = [
    # allauth
    path('accounts/google/login/', views.google_login, name='google_login'),
    path('accounts/google/login/callback/', views.google_callback, name='google_callback'),
    path('accounts/facebook/login/', views.facebook_login, name='facebook_login'),
    path('accounts/facebook/login/callback/', views.facebook_callback, name='facebook_callback'),
    path('accounts/naver/login/', views.naver_login, name='naver_login'),
    path('accounts/naver/login/callback/', views.naver_callback, name='naver_callback'),
    path('accounts/kakao/login/', views.kakao_login, name='kakao_login'),
    path('accounts/kakao/login/callback/', views.kakao_callback, name='kakao_callback'),
]

 

이제 로그인을 하면 커스텀 함수로 연결된다.

 

그 전에 로그인 버튼에 연결된 경로를 아래와 같이 바꾼다. next변수에 현재 위치를 담아서 서버로 보내는 것이다.

 

<a href="{% url 'google_login' %}?next={{ request.get_full_path }}">
<a href="{% url 'facebook_login' %}?next={{ request.get_full_path }}">
<a href="{% url 'naver_login' %}?next={{ request.get_full_path }}">
<a href="{% url 'kakako_login' %}?next={{ request.get_full_path }}">

 

view.py

def google_login(request):
    state = str(uuid.uuid4())  # 고유 값 생성
    request.session['google_state'] = state  # 세션에 저장

    # 원래 URL을 세션에 저장
    next_url = request.GET.get('next', '/')
    request.session['next_url'] = next_url

    app = SocialApp.objects.get(provider='google')
    client_id = app.client_id
    redirect_uri = request.build_absolute_uri('/accounts/google/login/callback/')

    # Google OAuth2 인증 URL 구성
    params = {
        'response_type': 'code',
        'client_id': client_id,
        'redirect_uri': redirect_uri,
        'scope': 'profile email',
        'access_type': 'offline',
        'prompt': 'select_account',
        'state': state,
    }

    authorization_url = f'https://accounts.google.com/o/oauth2/v2/auth?{urlencode(params)}'

    return redirect(authorization_url)

 

Google 인증 후 '/accounts/google/login/callback/'로 리다이렉팅 된다.

 

인증에 필요한 매개변수에 대한 설명은 다음과 같다.

 

- response_type='code': 인증 코드 방식을 사용

- client_id: Google OAuth2 클라이언트 ID.

- redirect_uri: 인증 후 Google이 리디렉션할 URL.

- scope='profile email': 사용자의 프로필 정보와 이메일을 요청.

- access_type='offline': 오프라인 액세스를 요청하여 리프레시 토큰을 획득.

- prompt='select_account': 로그인할 때 사용자에게 계정 선택 화면을 표시.

 

prompt는 옵션에 따라 인증 동작이 달라진다.

- none: 사용자 상호작용 없이 인증을 처리

- select_account: 사용자에게 계정을 선택할 수 있는 화면을 표시

- consent: 사용자의 동의를 강제로 다시 요청

- login: 항상 로그인 화면을 표시하며, 이미 로그인된 상태라 하더라도 계정을 다시 입력

 

인증 후 호출되는 callback 함수는 아래와 작성하자.

 

def google_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')

    saved_state = request.session.pop('google_state', None)

    if not code or state != saved_state:
        return JsonResponse({'error': 'Invalid code or state parameter'}, status=400)

    token_url = 'https://oauth2.googleapis.com/token'
    app = SocialApp.objects.get(provider='google')
    client_id = app.client_id
    client_secret = app.secret
    redirect_uri = request.build_absolute_uri('/accounts/google/login/callback/')

    # 토큰 요청
    token_data = {
        'code': code,
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
        'state': state,
        'grant_type': 'authorization_code'
    }
    response = requests.post(token_url, data=token_data)
    response_data = response.json()

    # 토큰 처리 오류 검사
    if response.status_code != 200 or 'access_token' not in response_data:
        return JsonResponse({'error': 'Failed to obtain access token'}, status=400)

    access_token = response_data['access_token']

    # 사용자 정보 요청
    user_info_url = 'https://www.googleapis.com/oauth2/v2/userinfo'
    headers = {'Authorization': f'Bearer {access_token}'}
    user_info_response = requests.get(user_info_url, headers=headers)
    user_info = user_info_response.json()

    # 사용자 인증 및 세션 처리
    id = user_info.get('id')
    email = user_info.get('email')
    first_name = user_info.get('given_name')
    last_name = user_info.get('family_name')

    if id:
        user, created = User.objects.get_or_create(username=id, defaults={'first_name': first_name, 'last_name': last_name, 'email': email})
        if created:
            user.set_unusable_password()  # 비밀번호 설정을 하지 않음
            user.save()

        user.backend = 'django.contrib.auth.backends.ModelBackend'
        auth_login(request, user)

        uid = id
        token_value = access_token

        expires_in = 24

    # 원래 URL로 리디렉션
    next_url = request.session.pop('next_url', '/')

    return redirect(next_url)

 

사용자가 Google 로그인 Google 제공한 인증 코드를 사용하여 액세스 토큰을 요청하고, 이를 통해 사용자의 프로필 정보를 가져온다. 이후 해당 정보를 이용해 Django에서 사용자를 인증하고 로그인 처리를 한다. 다른 SNS 비슷하다. 아래를 참고하자.

 

Facebook

def facebook_login(request):
    state = str(uuid.uuid4())  # 고유 값 생성
    request.session['facebook_state'] = state  # 세션에 저장

    # 원래 URL을 세션에 저장
    next_url = request.GET.get('next', '/')
    request.session['next_url'] = next_url

    app = SocialApp.objects.get(provider='facebook')
    client_id = app.client_id
    redirect_uri = request.build_absolute_uri('/accounts/facebook/login/callback/')

    params = {
        'response_type': 'code',
        'client_id': client_id,
        'redirect_uri': redirect_uri,
        'state': state,
        'scope': 'email,public_profile',
    }

    authorization_url = f'https://www.facebook.com/v12.0/dialog/oauth?{urlencode(params)}'

    return redirect(authorization_url)

def facebook_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')

    saved_state = request.session.pop('facebook_state', None)
    if not code or state != saved_state:
        return JsonResponse({'error': 'Invalid code or state parameter'}, status=400)

    app = SocialApp.objects.get(provider='facebook')
    client_id = app.client_id
    client_secret = app.secret
    redirect_uri = request.build_absolute_uri('/accounts/facebook/login/callback/')

    # 토큰 요청 URL 구성
    token_url = 'https://graph.facebook.com/v12.0/oauth/access_token'

    # 토큰 요청
    token_data = {
        'code': code,
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
        'state': state
    }

    response = requests.post(token_url, data=token_data)
    response_data = response.json()

    # 토큰 처리 오류 검사
    if response.status_code != 200 or 'access_token' not in response_data:
        return JsonResponse({'error': 'Failed to obtain access token'}, status=400)

    access_token = response_data['access_token']

    # 액세스 토큰을 사용하여 사용자 정보 요청
    user_info_url = 'https://graph.facebook.com/me'
    user_info_params = {
        'fields': 'id,name,email,picture',
        'access_token': access_token,
    }

    user_info_response = requests.get(user_info_url, params=user_info_params)
    user_info = user_info_response.json()

    if 'error' in user_info:
        return JsonResponse({'error': user_info['error']['message']}, status=400)

    id = user_info.get('id')
    name = user_info.get('name')
    email = user_info.get('email')
    user_info['picture'] = user_info.get('picture')['data']['url']
    first_name = name.split(' ')[0]
    last_name = ''
    try:
        last_name = name.split(' ')[1]
    except:
        pass

    # 사용자 정보로 로그인 처리
    if id:
        user, created = User.objects.get_or_create(username=id, defaults={'first_name': first_name, 'last_name': last_name, 'email': email})
        if created:
            user.set_unusable_password()  # 비밀번호 설정을 하지 않음
            user.save()

        user.backend = 'django.contrib.auth.backends.ModelBackend'
        auth_login(request, user)

        uid = id
        token_value = access_token

        expires_in = 24

    # 원래 URL로 리디렉션
    next_url = request.session.pop('next_url', '/')
    return redirect(next_url)

 

Naver

def naver_login(request):
    state = str(uuid.uuid4())  # 고유 값 생성
    request.session['naver_state'] = state  # 세션에 저장

    # 원래 URL을 세션에 저장
    next_url = request.GET.get('next', '/')
    request.session['next_url'] = next_url

    app = SocialApp.objects.get(provider='naver')
    client_id = app.client_id
    redirect_uri = request.build_absolute_uri('/accounts/naver/login/callback/')

    params = {
        'response_type': 'code',
        'client_id': client_id,
        'redirect_uri': redirect_uri,
        'state': state,
    }

    authorization_url = f'https://nid.naver.com/oauth2.0/authorize?{urlencode(params)}'

    return redirect(authorization_url)

def naver_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')
    # 세션에 저장된 STATE 값과 비교
    saved_state = request.session.pop('naver_state', None)
    if not code or state != saved_state:
        return JsonResponse({'error': 'Invalid code or state parameter'}, status=400)

    app = SocialApp.objects.get(provider='naver')
    client_id = app.client_id
    client_secret = app.secret
    redirect_uri = request.build_absolute_uri('/accounts/naver/login/callback/')

    # 토큰 요청 URL 구성
    token_url = 'https://nid.naver.com/oauth2.0/token'

    # 토큰 요청
    token_data = {
        'grant_type': 'authorization_code',
        'code': code,
        'state': state,
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
    }

    response = requests.post(token_url, data=token_data)
    response_data = response.json()

    # 토큰 처리 오류 검사
    if 'error' in response_data:
        return JsonResponse({'error': response_data['error_description']}, status=400)

    access_token = response_data['access_token']

    # 액세스 토큰을 사용하여 사용자 정보 요청
    user_info_url = 'https://openapi.naver.com/v1/nid/me'
    user_info_headers = {
        'Authorization': f'Bearer {access_token}',
    }

    user_info_response = requests.get(user_info_url, headers=user_info_headers)
    user_info = user_info_response.json()
    if user_info['resultcode'] == '024':
        return JsonResponse({'error': user_info['message']}, status=400)
    user_info = user_info['response']
    id = user_info.get('id')
    user_info['name'] = user_info.get('nickname')
    name = user_info['name']
    email = user_info.get('email')
    user_info['picture'] = user_info.get('profile_image')
    first_name = name.split(' ')[0]
    last_name = ''
    try:
        last_name = name.split(' ')[1]
    except:
        pass

    # 사용자 정보로 로그인 처리
    if id:
        user, created = User.objects.get_or_create(username=id, defaults={'first_name': first_name, 'last_name': last_name, 'email': email})
        if created:
            user.set_unusable_password()  # 비밀번호 설정을 하지 않음
            user.save()

        user.backend = 'django.contrib.auth.backends.ModelBackend'
        auth_login(request, user)

        uid = id
        token_value = access_token

        expires_in = 24

    # 원래 URL로 리디렉션
    next_url = request.session.pop('next_url', '/')
    return redirect(next_url)

 

Kakao

def kakao_login(request):
    state = str(uuid.uuid4())  # 고유 값 생성
    request.session['kakao_state'] = state  # 세션에 저장

    # 원래 URL을 세션에 저장
    next_url = request.GET.get('next', '/')
    request.session['next_url'] = next_url

    app = SocialApp.objects.get(provider='kakao')
    client_id = app.client_id
    client_secret = app.secret
    redirect_uri = request.build_absolute_uri('/accounts/kakao/login/callback/')

    params = {
        'response_type': 'code',
        'state': state,
        'client_id': client_id,
        # 'client_secret': client_secret,
        'redirect_uri': redirect_uri,
    }

    authorization_url = f'https://kauth.kakao.com/oauth/authorize?{urlencode(params)}'

    return redirect(authorization_url)

def kakao_callback(request):
    code = request.GET.get('code')
    state = request.GET.get('state')
    saved_state = request.session.pop('kakao_state', None)
    if not code or state != saved_state:
        return JsonResponse({'error': 'Invalid code or state parameter'}, status=400)

    app = SocialApp.objects.get(provider='kakao')
    client_id = app.client_id
    client_secret = app.secret
    redirect_uri = request.build_absolute_uri('/accounts/kakao/login/callback/')

    # 토큰 요청 URL 구성
    token_url = 'https://kauth.kakao.com/oauth/token'

    # 토큰 요청
    token_data = {
        'grant_type': 'authorization_code',
        'code': code,
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
        'state': state
    }

    response = requests.post(token_url, data=token_data)
    response_data = response.json()

    # 토큰 처리 오류 검사
    if response.status_code != 200 or 'access_token' not in response_data:
        return JsonResponse({'error': 'Failed to obtain access token'}, status=400)

    access_token = response_data['access_token']

    # 액세스 토큰을 사용하여 사용자 정보 요청
    user_info_url = 'https://kapi.kakao.com/v2/user/me'
    user_info_headers = {
        'Authorization': f'Bearer {access_token}',
    }

    user_info_response = requests.get(user_info_url, headers=user_info_headers)
    user_info = user_info_response.json()

    if 'error' in user_info:
        return JsonResponse({'error': user_info['error']['message']}, status=400)

    id = user_info.get('id')
    user_info['name'] = user_info['kakao_account']['profile']['nickname']
    name = user_info['name']
    user_info['email'] = user_info['kakao_account']['email']
    email = user_info['email']
    user_info['picture'] = user_info['kakao_account']['profile']['thumbnail_image_url']
    first_name = name.split(' ')[0]
    last_name = ''
    try:
        last_name = name.split(' ')[1]
    except:
        pass

    # 사용자 정보로 로그인 처리
    if id:
        user, created = User.objects.get_or_create(username=id, defaults={'first_name': first_name, 'last_name': last_name, 'email': email})
        if created:
            user.set_unusable_password()  # 비밀번호 설정을 하지 않음
            user.save()

        user.backend = 'django.contrib.auth.backends.ModelBackend'
        auth_login(request, user)

        uid = id
        token_value = access_token

        expires_in = 24

    # 원래 URL로 리디렉션
    next_url = request.session.pop('next_url', '/')
    return redirect(next_url)

 

이제, 로그인 후에 해당페이지에 머물러 있으며 로그인도 매번 할 필요가 없어졌다.

728x90