python Flask 이용해 구글 로그인 구현하기

작성: 2021.03.24

수정: 2021.03.24

읽는시간: 00 분

Programming/Python

반응형

중간프로젝트에서 네이버, 카카오, 구글등의 OAuth 로그인을 구현하고 싶었지만 시간에 치여 불가능 했었는데요, 이번에 조별 과제를 통해 기회가 생겨 파이썬에서 구현 해 보았습니다.


developers.google.com/identity/sign-in/web/sign-in

 

Integrating Google Sign-In into your web app

Google Sign-In manages the OAuth 2.0 flow and token lifecycle, simplifying your integration with Google APIs. A user always has the option to revoke access to an application at any time. This document describes how to complete a basic Google Sign-In integr

developers.google.com

위에서 google developer 페이지를 참고하며 기본적인 먼저 셋팅을 했습니다.

 

1. Credentials page에 접속합니다.

2. Credentials -> CREATE CREDENTIALS 를 클릭합니다.

 

 

 

 

3. O Auth client ID를 클릭합니다.

 

 

 

4.  Application type은 Web application 을 선택합니다.

 

 

5.  Name은 적당히 원하는 것을 입력합니다.

 

 

 

6. Authorised redirect URI를 등록 합니다. 제가 아래에서 공유할 python 코드를 사용하실거면, https://127.0.0.1:5000/login/callback  을 반드시 입력해야 합니다. 본인의 어플리케이션을 등록하시면, 로그인 후에 Redirect 될 페이지를 등록하면 됩니다.

 

 

7.  어플리케이션이 등록이 완료되었습니다.

 

 

 

8. 해당 어플리케이션을 클릭하고 들어가면 ClientID 와 Client secret을 확인 할 수 있습니다. 우측에 있는 값입니다.

 

 

 


 

이제 python에서 코드를 작성합니다. 코드는 아래 깃허브 링크에서 참고해서 작성했습니다.

github.com/realpython/materials/tree/master/flask-google-login?__s=nk12ip8oq81gce45lqjk

 

realpython/materials

Bonus materials, exercises, and example projects for our Python tutorials - realpython/materials

github.com

 

제일 먼저 pip나 conda install로 아래 목록 중 없는 라이브러리는 설치합니다.

 

requests==2.21.0

Flask==1.0.2

oauthlib==3.0.1

pyOpenSSL==19.0.0

Flask-Login==0.4.1

 

db.py 파일입니다.

# http://flask.pocoo.org/docs/1.0/tutorial/database/
import sqlite3

import click
from flask import current_app, g
from flask.cli import with_appcontext


def get_db():
    if "db" not in g:
        g.db = sqlite3.connect(
            "sqlite_db", detect_types=sqlite3.PARSE_DECLTYPES
        )
        g.db.row_factory = sqlite3.Row

    return g.db


def close_db(e=None):
    db = g.pop("db", None)

    if db is not None:
        db.close()


def init_db():
    db = get_db()

    with current_app.open_resource("schema.sql") as f:
        db.executescript(f.read().decode("utf8"))


@click.command("init-db")
@with_appcontext
def init_db_command():
    """Clear the existing data and create new tables."""
    init_db()
    click.echo("Initialized the database.")


def init_app(app):
    app.teardown_appcontext(close_db)
    app.cli.add_command(init_db_command)

 

user.py 파일입니다.

from flask_login import UserMixin

from googleLogin.db import get_db


class User(UserMixin):
    def __init__(self, id_, name, email, profile_pic):
        self.id = id_
        self.name = name
        self.email = email
        self.profile_pic = profile_pic

    @staticmethod
    def get(user_id):
        db = get_db()
        user = db.execute(
            "SELECT * FROM user WHERE id = ?", (user_id,)
        ).fetchone()
        if not user:
            return None

        user = User(
            id_=user[0], name=user[1], email=user[2], profile_pic=user[3]
        )
        return user

    @staticmethod
    def create(id_, name, email, profile_pic):
        db = get_db()
        db.execute(
            "INSERT INTO user (id, name, email, profile_pic)"
            " VALUES (?, ?, ?, ?)",
            (id_, name, email, profile_pic),
        )
        db.commit()

 

schema.sql 파일입니다.

CREATE TABLE user (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT UNIQUE NOT NULL,
  profile_pic TEXT NOT NULL
);

 

 

마지막으로 app.py 파일입니다.

반드시 본인이 위에서 설정한 어플리케이션의 GOOGLE_CLIENT_ID 와 GOOGLE_CLIENT_SECRET 을 써주셔야 합니다.

# Python standard libraries
import json
import os
import sqlite3

from flask import Flask, redirect, request, url_for
from flask_login import (
    LoginManager,
    current_user,
    login_required,
    login_user,
    logout_user,
)
from oauthlib.oauth2 import WebApplicationClient
import requests

from googleLogin.db import init_db_command
from googleLogin.user import User


# Third party libraries
# Internal imports
# Configuration
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", '여기에 CLIENT_ID를 입력')
GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", '여기에 CLIENT_SECRET 입력')
GOOGLE_DISCOVERY_URL = (
    "https://accounts.google.com/.well-known/openid-configuration"
)

# Flask app setup
app = Flask(__name__)
app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24)

# User session management setup
# https://flask-login.readthedocs.io/en/latest
login_manager = LoginManager()
login_manager.init_app(app)


@login_manager.unauthorized_handler
def unauthorized():
    return "You must be logged in to access this content.", 403


# Naive database setup
try:
    init_db_command()
except sqlite3.OperationalError:
    # Assume it's already been created
    pass

# OAuth2 client setup
client = WebApplicationClient(GOOGLE_CLIENT_ID)


# Flask-Login helper to retrieve a user from our db
@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id)


@app.route("/")
def index():
    if current_user.is_authenticated:
        return (
            "<p>{}님 어서오세요!! 로그인 되었습니다.! 당신의 이메일 : {}</p>"
            "<div><p>당신의 구글 프로필 사진 : </p>"
            '<img src="{}" alt="Google profile pic"></img></div>'
            '<a class="button" href="/logout">로그아웃하기</a>'.format(
                current_user.name, current_user.email, current_user.profile_pic
            )
        )
    else:
        return '<a class="button" href="/login">클릭해서 구글 로그인하기</a>'


@app.route("/login")
def login():
    # Find out what URL to hit for Google login
    google_provider_cfg = get_google_provider_cfg()
    authorization_endpoint = google_provider_cfg["authorization_endpoint"]

    # Use library to construct the request for login and provide
    # scopes that let you retrieve user's profile from Google
    request_uri = client.prepare_request_uri(
        authorization_endpoint,
        redirect_uri=request.base_url + "/callback",
        scope=["openid", "email", "profile"],
    )
    return redirect(request_uri)


@app.route("/login/callback")
def callback():
    # Get authorization code Google sent back to you
    code = request.args.get("code")

    # Find out what URL to hit to get tokens that allow you to ask for
    # things on behalf of a user
    google_provider_cfg = get_google_provider_cfg()
    token_endpoint = google_provider_cfg["token_endpoint"]

    # Prepare and send request to get tokens! Yay tokens!
    token_url, headers, body = client.prepare_token_request(
        token_endpoint,
        authorization_response=request.url,
        redirect_url=request.base_url,
        code=code,
    )
    token_response = requests.post(
        token_url,
        headers=headers,
        data=body,
        auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET),
    )

    # Parse the tokens!
    client.parse_request_body_response(json.dumps(token_response.json()))

    # Now that we have tokens (yay) let's find and hit URL
    # from Google that gives you user's profile information,
    # including their Google Profile Image and Email
    userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
    uri, headers, body = client.add_token(userinfo_endpoint)
    userinfo_response = requests.get(uri, headers=headers, data=body)

    # We want to make sure their email is verified.
    # The user authenticated with Google, authorized our
    # app, and now we've verified their email through Google!
    if userinfo_response.json().get("email_verified"):
        unique_id = userinfo_response.json()["sub"]
        users_email = userinfo_response.json()["email"]
        picture = userinfo_response.json()["picture"]
        users_name = userinfo_response.json()["given_name"]
    else:
        return "User email not available or not verified by Google.", 400

    # Create a user in our db with the information provided
    # by Google
    user = User(
        id_=unique_id, name=users_name, email=users_email, profile_pic=picture
    )

    # Doesn't exist? Add to database
    if not User.get(unique_id):
        User.create(unique_id, users_name, users_email, picture)

    # Begin user session by logging the user in
    login_user(user)

    # Send user back to homepage
    return redirect(url_for("index"))


@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("index"))


def get_google_provider_cfg():
    return requests.get(GOOGLE_DISCOVERY_URL).json()


if __name__ == "__main__":
    app.run(ssl_context="adhoc")

 

이제 실행해봅니다.

처음 실행시에는 "Initialized the database." 라는 메시지만 나옵니다. 한번 더 실행하면 정상적으로 서버가 실행됩니다.

 

 

 

브라우저에 https://127.0.0.1:5000/ 를 입력해 접속합니다.

 

구글 로그인 하기를 클릭한 뒤에

 

 

 

 

본인의 google 아이디로 로그인 합니다.

 

 

 

비밀번호까지 입력 한 뒤에 다음 버튼을 누르면

 

 

 

 

정상적으로 로그인 하며 로그인 정보를 불러오는 것을 확인 할 수 있습니다! 위의 코드를 참고해 본인의 프로젝트에 직접 로그인 기능을 구현하실 수 있습니다.

 

 

 

 

+ 추가 

로그인할때 로그인 버튼을 좀 더 그럴싸하게 버튼 이미지로 바꿔보았습니다. !

static 폴더를 만들어서 버튼 이미지를 넣어두어야 합니다.

 

static 폴더를 경로에 잡아줍니다.

app = Flask(__name__, static_url_path="", static_folder='static')

 

app.route에서도 else: return 부분에 ( 최초 접속시 ) 

구글 로그인 하기 라고 텍스트로 써두었던 부분을 이미지로 바꾸어줬습니다.

@app.route("/")
def index():
    if current_user.is_authenticated:
        return (
            "<p>{}님 어서오세요!! 로그인 되었습니다.! 당신의 이메일 : {}</p>"
            "<div><p>당신의 구글 프로필 사진 : </p>"
            '<img src="{}" alt="Google profile pic"></img></div>'
            '<a class="button" href="/logout">로그아웃하기</a>'.format(
                current_user.name, current_user.email, current_user.profile_pic
            )
        )
    else:
        return '<a class="button" href="/login"><img src="button.png"></a>'

 

 

이제 버튼을 눌러 로그인 할 수 있습니다.

반응형