회원가입 기능 구현하기

자훈·2023년 10월 5일
0

개인프로젝트

목록 보기
7/10
post-thumbnail

📌 python 코드

import os
from flask import Flask, render_template, request, flash, redirect, url_for, session
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo
from werkzeug.security import generate_password_hash, check_password_hash

#os를 통해 절대경로 설정하기. 
curent_directory = os.path.abspath(os.path.dirname(__file__))
database_path = os.path.join(curent_directory, 'database.db')
# 현재 경로를 잘못설정했음. todolist/database.db로 했을 때 operation error 가

app = Flask(__name__)
app.config['SECRET_KEY'] = 'dev'
# 데이터 베이스 URI 설정
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{database_path}'
db = SQLAlchemy(app)


# 사용자 모델 정의
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)


# 회원가입을 위한 폼 정의
class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Register')


# 로그인을 위한 폼 정의
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')


# 홈페이지
@app.route('/')
def home():
    return render_template('home.html')


# 회원가입 뷰
@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = generate_password_hash(form.password.data, method='sha256')
        new_user = User(username=form.username.data, password=hashed_password)
        db.session.add(new_user)
        db.session.commit()
        flash('Your account has been created!', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)


# 로그인 뷰
@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and check_password_hash(user.password, form.password.data):
            session['user_id'] = user.id  # 세션에 사용자 ID 저장
            flash('Login successful!', 'success')
            return redirect(url_for('home'))
        else:
            flash('Login failed. Please check your username and password.', 'danger')
    return render_template('login.html', form=form)


# 로그아웃 뷰
@app.route('/logout')
def logout():
    session.pop('user_id', None)  # 세션에서 사용자 ID 삭제
    flash('You have been logged out.', 'info')
    return redirect(url_for('home'))


def init_db():
    with app.app_context():
        db.create_all()


if __name__ == '__main__':
    init_db()  # 데이터베이스 초기화
    app.run(debug=True)

📌 session

session: 세션(Session)은 웹 애플리케이션의 상태 정보를 클라이언트와 서버 간에 저장하고 유지하는 데 사용되는 메커니즘 중 하나이다. 웹 사이트의 여러 페이지에 걸쳐 사용되는 사용자 정보를 저장하는 방법을 의미한다. 사용자가 브라우저를 닫아 서버와의 연결을 끝내는 시점까지를 세션이라고 한다.

해당 코드에서는
session['user_id'] = user.id를 통해 세션에 사용자 id를 저장하고 seesion.pop 을 통해 세션에 있던 데이터를 제거한다.

📌 Flask_wtf

WT는 Web form의 줄임말. 웹고 관련된 폼의 작업을 지원한다는 의미이다.

flask_wtf: form이란 사용자로부터 정보를 입력받는 방식을 말하고, flask_wtf는 그걸 관리해주는 패키지이다. 사용하기 위해서는 flasK_wtf 에서 주는 Flaskform 을 상속받아 사용해야한다.

from flask_wtf import Flaskform
from wtforms import StringField, validators, PassworldField, BooleanField
# 등등 여러가지 사용할 수 있다. 

입력받을 형식을 지정하고, validator을 통해 데이터의 유효성 검사(길이, 동일한 값인지)가 가능하다. form을 받을 endpoin를 작성해주고, form클래스의 instance(초기값, 호출)을 하여 데이터를 받아온다.
template로 넘겨줄 때는 form parameter 형식으로 넘겨주어야 사용가능하다.

wtforms.validator- Datarequired, EqaulTo, Email, length, NumberRange

Datarequired:
필드가 빈 문자열 또는 None값이 아니여야 한다. 데이터가 필수적인 경우에 사용된다. 문법은 아래와 같다.

from wtforms.validators import DataRequired

username = StringField('Username', validators=[DataRequired()])

EquaulTo:
두개의 필드 값이 동일한지 확인한다. 비밀번호 확인과 같은 절차를 이용할 때, 필요하다.

from wtforms.validators import EqualTo

password = PasswordField('Password' validators=[DataRequired=()])
password_confirm = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])

Email:
필드가 유효한 이메일 주소여야 한다.

from wtforms.validators import Email
email = StringField('email', validators=[Email()])

Length:
필드의 길이 (문자수)를 지정된 범위 내로 제한한다. min 과 max 인수를 사용하여 최소 최대를 설정할 수 있다.

from wtforms.validators import Length
username = StringField('username', validators=[Length(min=3, max=50)])

NumberRange:
숫자 필드의 값이 지정된 숫자 범위 내에 있어야 한다. min 및 max 인수를 사용하여 범위를 설정할 수 있다.

from wtforms.validators import NumberRange
age = IntegerField('Age', validators=[NumberRange(min=1,max=1000])

📌query.filter_by

query.filter_by: 는 SQLAlchemy에서 사용되는 메서드이다. 모델을 쿼리하고 필터링하는데 사용되어진다. 다시 말해 특정조건에 맞는 레코드를 검색하는데 사용되어진다. 주로 데이터베이스 테이블의 열과 관련된 조건을 지정하고 해당 조건에 맞는 레코드를 검색하는 데 사용된다.

해당 코드에서는

user = User.query.filter_by(username=form.username.data).first()

쿼리필터에 first()메서드 (객체에 속하는 함수)를 통해, 첫 번째 일치하는 레코드를 반환한다.

📌generate_password_hash, check_password_hash

generate_password_hash:
사용자 비밀번호를 해시로 변환하여 저장할 때 사용된다. 해싱은 원래 비밀번호를 불가능하게 되돌릴 수 있는 암호화된 문자열로 변환하는 프로세스이다. 이것은 비밀번호를 안전하게 저장하고 인증 과정에서 검증하는 데 사용된다.

여기서 해싱알고리즘이 사용되는데,
method='sha256 이 바로 그것이다. 이 알고리즘은 입력 데이터를 고정 길이의 해시 값으로 변환하는 데 사용되며, 같은 입력에 대해 항상 동일한 해시를 출력합니다. 이 알고리즘은 일반적으로 권장되는 것 중 하나이다.

check_password_hash:
저장된 해시와 사용자가 제출한 비밀번호를 비교하여 인증을 수행할 때 사용된다. 즉, 사용자가 로그인 시도할 때 입력한 비밀번호를 검증하는 데 사용된다.
사용자가 입력한 비밀번호에 대한 동일한 해시 함수를 사용하여, 해싱한 후 저장된 해시와 비교하는 과정을 거친다.

📌html 코드

📌 home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <div class="row mt-5 text-center">
        <h1><a href="/" style="text-decoration: none"> home page </a></h1>
    </div>
    <div>
        <button class="btn btn-outline-secondary"><a href="{{ url_for('login') }}" style="text-decoration: none">로그인</a></button>
        <button class="btn btn-outline-secondary"><a href="{{ url_for('register') }}" style="text-decoration: none">회원가입</a></button>
    </div>

    <div class="footer-copyright py-3">
        © 2023 Copyright:
        <a href="https://velog.io/@hope77" target="_blank"> Jahunjang </a>
    </div>

</body>
</html>

📌 login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로그인 시스템</title>
</head>
<body>
   <div class="container">
    <h1><a href="/" style="text-decoration: none"> home page </a></h1>
        <div class="row mt-5">
            <nav aria-label="breadcrumb">
                <ol class="breadcrumb">
                    <li class="breadcrumb-item"><a href="/"></a></li>
                    <li class="breadcrumb-item"><a href="/register">회원가입</a></li>
                </ol>
            </nav>
        </div>
        <div class="row mt-5">
            <div class="col-12">
                <form method="POST" action="/login">
                    {{ form.hidden_tag() }}
                    <div class="form-group">
                        {{ form.username.label("아이디")}}
                        {{ form.username(class="form-control", placeholder="아이디") }}
                    </div>
                    <div class="form-group">
                         {{ form.password.label("비밀번호")}}
                         {{ form.password(class="form-control", placeholder="비밀번호") }}
                    </div>
                        <button type="submit" class="btn btn-outline-primary mb-3">로그인</button>
                </form>

                    {% with messages = get_flashed_messages() %}
                    {% if messages %}
                    <ul class="flashes">
                        {% for message in messages %}
                        <li>{{ message }}</li>
                        {% endfor %}
                    </ul>
                    {% endif %}
                    {% endwith %}

                    <div>
                        <a href="/register" style="text-decoration: none"> 회원가입 </a>
                    </div>

                    <div class="footer-copyright py-3">
                        © 2023 Copyright:
                        <a href="https://velog.io/@hope77" target="_blank"> Jahunjang </a>
                    </div>
            </div>
        </div>
    </div>
</body>
</html>

📌register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>회원가입</title>
</head>
<body>
    <h1><a href="/" style="text-decoration: none"> home page </a></h1>
    <h1>회원가입</h1>
    <div class="container">
        <div class="row mt-5">
            <nav aria-label="breadcrumb">
                <ol class="breadcrumb">
                    <li class="breadcrumb-item"><a href="/"></a></li>
                    <li class="breadcrumb-item"><a href="/register">회원가입</a></li>
                </ol>
              </nav>
        </div>

        <div class="row mt-5">
            <div class="col-9">
                <form method="POST">
                    {{ form.hidden_tag() }}
                    <div class="form-group">
                        {{ form.username.label("아이디") }}
                        {{ form.username(class="form-control", placeholder="아이디", size=20) }}
                    </div>
                    <div class="form-group">
                        {{ form.password.label("비밀번호") }}
                        {{ form.password(class="form-control", placeholder="비밀번호", size=20) }}
                    </div>
                    <div class="form-group">
                        {{ form.confirm_password.label("비밀번호확인")}}
                        {{ form.confirm_password(class="form-control", plcaeholder="비밀번호확인", size=20) }}
                    </div>
                    <div>
                        <button type="submit" class="btn btn-outline-primary mb-3">제출</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
</body>
</html>

📌 동작

로그인 성공시 홈화면으로 돌아가야한다. 등록되어있지 않다면, 실패했다는 문구가 나오게 된다. 회원가입을 하고나서 로그인을 하면, 정상적으로 홈페이지로 이동하는 것을 확인하였다. 이 기능을 구현하는 이유는, 진행중인 라이엇api 프로젝트에 추가할 기능이기 때문이다. 이제 회원가입을 하고나서 게시판을 만들어보자!

0개의 댓글