WEB/Backend

[Python] Flask 로그인/회원가입 기능 만들기

Jerry_K 2024. 4. 12. 15:54

✏️ 이전 내용

 

[Python] Firebase 시작하기

🙄 Firebase란 ? 구글이 소유하고 있는 모발일 애플리케이션 개발 플랫폼으로, 개발자가 모바일 및 웹 애플리케이션을 모두 쉽게 생성,실행 및 확장 할 수 있도록 한다. (데이터 저장은 NoSQL 문서

fishking.tistory.com

(이 포스팅은 지난번에 포스팅한 firebase를 할 줄 안다는 가정으로 한다.)

혹시 firebase 프로젝트 생성을 모른다면 위의 포스트를 보고오자.

 

📚포스트 내용

이번 포스트에서는 flask로 로컬 서버를 만들고 로그인 기능을 간단히 만들어보려고 한다.

(CSS/JS 부분은 들어가지 않는다.)

코드 진행 순서대로 포스팅을 해보려 했는데, 너무 복잡해져서 핵심 부분만 쓴다.


🎄파일 tree 구조

│  app.py
│  DB_handler.py
│  README.md

├─auth
│      firebaseAuth.json

├─static
│  └─image
│      └─css
└─templates
        base.html
        index.html
        login.html
        signin.html

 

- app.py : flask 서버 구현

- DB_handler.py : firebase 클래스 / 객체 생성

- firebaseAuth.json : firebase SDK 키 

- base.html : 코드 공통 부분

- index.html : 시작 페이지

- login.html : 로그인 페이지

- signin.html : 회원가입 페이지

 

(해당 파일은 다 만들었다는 전제로 진행 / static 폴더는 형식상 생성)


📗핵심 부분 정의

 

📃app.py

- index 함수

@app.route("/")
def index():
    if "uid" in session :
        user = session["uid"]
    else : 
        user = "Login"
    return render_template("index.html",user=user)

로그인을 하면 session["uid"] 세션을 새로 만들고, 로그아웃을 하면 만들었던 세션을 지운다.

user 변수를 세션 uid 존재여부에 따라 정의된다.  변수 user는 index.html로 렌더링되어 전달된다. 

 

( session["uid"] 는 login_done 함수에서 정의된다.)

 

 

- signin 함수

@app.route("/signin")
def signin():
    return render_template("signin.html")

render_template으로 signin.html 을 렌더링한다. 

 

 

- signin_done 함수

@app.route("/signin_done",methods=["GET"])
def signin_done():
    uid = request.args.get("id")
    pwd = request.args.get("pwd")
    name = request.args.get("name")
    if DB.signin(_id_=uid,pwd=pwd,name=name):
        return redirect(url_for("index"))
    else :
        flash("이미 존재하는 아이디입니다.")
        return redirect(url_for("signin"))

methods는 GET 방식으로  signin 페이지로부터  uid / pwd / name을 가져온다. 

DB.signin은  내가 정의한 객체로 True 값이 리턴되면 중복되는 아이디가 없다는 걸 의미하여 회원가입에 성공하고,

False 값이 리턴되면 중복되는 아이디가 있다는 의미로 flash로 메시지를 전달하고 signin 페이지에 머무른다.

(DB 클래스는 app.py  다음 설명)

 

 

- login 함수

@app.route("/login")
def login():
    if "uid" in session : 
        return redirect(url_for("index"))
    return render_template("login.html")

session에 uid가 존재(로그인 상태)하면 index 페이지로 이동하고, 

그게 아니면 login 페이지로 이동한다.

 

 

- login_done 함수

@app.route("/login_done",methods=["GET"])
def login_done():
    uid = request.args.get("id")
    pwd = request.args.get("pwd")
    if DB.login(uid,pwd) :
        session["uid"] = uid 
        return redirect(url_for("index"))
    else : 
        flash("아이디가 없거나 비밀번호가 틀립니다.")
        return redirect(url_for("login"))

signin_done과 같은 방식으로 GET 방식이다. 

login 페이지로부터 입력 받은 id와 pwd를 각각 request 하여 uid, pwd로 저장한다. 

DB.login은 클래스의 객체로 입력 정보들이 데이터베이스와 일치하면 True를, 일치하지 않으면 False를 반환한다.

True가 반환될 경우 session["uid"]를 새로 정의하고 index 페이지로 이동한다.

False가 반활 될 경우 flash 함수를 통해 메시지를 전달하고, login 페이지에 머무른다.

(DB 클래스는 app.py  다음 설명)

 

 

- logout 함수

@app.route("/logout")
def logout():
   if "uid" in session :
        session.pop("uid")
        return redirect(url_for("index"))
   else :
       return redirect(url_for("login"))

로그아웃은 login에서 열어둔 session의 uid를 닫아주면 된다.

uid가 session에 존재(로그인 상태)하면 pop을 통해 닫아주고 index 페이지로 이동한다.

만일 로그인 상태가 아닌 경우는 login 페이지에 머무른다. 

 


📃DB_handler.py

📕DBModule 클래스

class DBModule : 
    def __init__(self):
        with open("./auth/firebaseAuth.json") as f :
            config = json.load(f)     
        firebase = pyrebase.initialize_app(config)
        self.db = firebase.database()

    def signin(self,_id_,pwd,name):
        informations = {
            "uname" : name,
            "pwd" : pwd,
        } 
        if self.sigin_verification(_id_):
            self.db.child("users").child(_id_).set(informations)
            return True
        else :
            return False

    def sigin_verification(self,uid):
        users = self.db.child("users").get().val()
        for i in users:
            if uid == i :
                return False
        return True
  
    def login(self,uid,pwd):
        users = self.db.child("users").get().val()
        try : 
            userinfo = users[uid]
            if userinfo["pwd"] == pwd :
                return True
            else :
                return False 
        except :
            return False

 

- __init__  함수

클래스의 생성자 __init__ 에서  Firebase SDK 저장했던 json 파일을 열어준다.

그리고 db를 선언하여 firebase와 연결 시켜준다.

 

 

- signin_verification 함수

회원가입 정보를 firebase 에서 넣기전에 중복되는 id가 있는지를 확인한다.

firebase 데이터베이스 users 값을 가져오고 회원가입하려는 uid와 비교를 한다. 

가입하려는 uid가 데이터베이스에 존재하는 값이면 False를 반환하고 (아이디 중복),

데이터베이스에 존재하지 않은 uid이면 True를 반환한다. 

 

 

- signin 함수

informations에서 uname과 pwd를 딕셔너리 형태로 만들고,

signin_verification을 통해 uid의 중복 여부를 체크한다. 

signin_verification 함수 리턴값은 bool 타입으로 True가 되면 회원정보를 데이터베이스에 저장하고

False가 되면 회원가입에 실패한다.

[데이터 구조]

├─ USERS  ─  ID 
│                        └─PASSWORD 
│                             NAME


 

- login 함수

데이터베이스에 users의 값들 가져오고 try 구문을 실행한다.  

입력받은 user의 ID가 존재하지 않으면  except 구문으로 넘어가 False를 반환한다.

반대로 입력받은 user의 ID가 존재하면, try 구문이 실행되어 해당 uid의 pwd를 입력받은 pwd와 비교한다.

 


📃 base.html

<body>
    <header>
        <h1>Welcom</h1>
    </header>

    {% block contents %}
    {% endblock contents %}

    {% block message %}
    {% endblock message %}

    {% block user%}
    {% endblock user%}
</body>

 

 

모든 html에 공통적으로 들어갈 기능들을 base.html에 넣어준다.

 


📃index.html

{%extends "base.html"%}

{% block contents %}
<h2>메인 페이지</h2>
{% endblock contents %}

{% block user %}
{% if user=="Login"%}
<a href="{{url_for('signin')}}">Signin</a>
<a href="{{url_for('login')}}">Login</a>
{% else %}
<p>user : {{user}}</p>
<a href="{{url_for('logout')}}">Logout</a>
{% endif %}
{% endblock user %}

 

extends를 통해  base.html을 가져온다.

 flask의 index를 렌더링 할 때,

전달 받은 user의 값이 "Login"인 경우(세션 열리지 않음) signin 또는 Login으로 가는 링크를 준다.

그게 아닌 경우에 user가 로그인 했다는 것을 알리기 위해 p 태그로 user를 나타내고 logout 링크를 준다.

 


📃 signin.html

{% extends "base.html" %}

{% block contents %}
<h2>회원가입</h2>
<form action="{{url_for('signin_done')}}" method="get">
    <p>Name :</p>
    <input type="text" name="name" id="name">
    <p>ID :</p>
    <input type="text" name="id" id="id">
    <p>Password :</p>
    <input type="password" name="pwd" id="pwd">
    <button type="submit">가입하기</button>
</form>
{% endblock contents %}

{% block message %}
{% with messages = get_flashed_messages() %}
{% if messages %}
<p>{{messages[0]}}</p>
{% endif %}
{% endwith %}
{% endblock message %}

 

똑같이 extends를 통해  base.html을 가져온다.

form 태그에 action을 통해 필요한 정보들을 기입 받는다.

그리고 signin_done으로 정보들을 전달한다.

block의 message쪽은 flash 함수의 메시지가 있는 경우 출력해준다.

 


📃 login.html

{% extends "base.html" %}

{% block contents %}
<h2>회원가입</h2>
<form action="{{url_for('login_done')}}" method="get">
    <p>ID :</p>
    <input type="text" name="id" id="id">
    <p>Password :</p>
    <input type="password" name="pwd" id="pwd">
    <button type="submit">로그인 하기</button>
</form>
{% endblock contents %}

{% block message %}
{% with messages = get_flashed_messages() %}
{% if messages %}
<p>{{messages[0]}}</p>
{% endif %}
{% endwith %}
{% endblock message %}

signin.html과 거의 유사하다. 

기입 받은 정보는 login_done으로 전달이 된다

아래 block message는 flash 함수로 부터 전달받은 값을 출력해주는 코드이다.

 


🧐마무리

간단한 회원가입/로그인을 구현해봤다.

session을 통해 사용자의 로그인 상태를 파악하는데, 다른 프로젝트에서도 유용하게 쓸 것 같다.

예를들어 카메라에 사용자의 uid를 주어 구분 할 수도 있을것 같다.

다음에는 이 코드에 글을 포스팅하는 방법을 알아보자.


📑코드 파일

base.html
0.00MB
index.html
0.00MB
login.html
0.00MB
signin.html
0.00MB
DB_handler.py
0.00MB
app.py
0.00MB