중간프로젝트에서 네이버, 카카오, 구글등의 OAuth 로그인을 구현하고 싶었지만 시간에 치여 불가능 했었는데요, 이번에 조별 과제를 통해 기회가 생겨 파이썬에서 구현 해 보았습니다.
developers.google.com/identity/sign-in/web/sign-in
위에서 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
제일 먼저 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 |