중간프로젝트에서 네이버, 카카오, 구글등의 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>'

이제 버튼을 눌러 로그인 할 수 있습니다.
'Programming > Python' 카테고리의 다른 글
python opencv 설치 (0) | 2021.03.25 |
---|---|
python, mysql, Flask 연동한 CRUD 예제 (0) | 2021.03.24 |
파이썬 Flask 사용법 - 3) Redirect 와 Forward (0) | 2021.03.23 |
파이썬 Flask 사용법 - 2) Parameter 보내기 GET/POST (0) | 2021.03.23 |
파이썬 Flask 사용법 - 1) Hello Flask (0) | 2021.03.23 |