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['user_id'] = user.id
를 통해 세션에 사용자 id를 저장하고 seesion.pop
을 통해 세션에 있던 데이터를 제거한다.
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
: 는 SQLAlchemy에서 사용되는 메서드이다. 모델을 쿼리하고 필터링하는데 사용되어진다. 다시 말해 특정조건에 맞는 레코드를 검색하는데 사용되어진다. 주로 데이터베이스 테이블의 열과 관련된 조건을 지정하고 해당 조건에 맞는 레코드를 검색하는 데 사용된다.
해당 코드에서는
user = User.query.filter_by(username=form.username.data).first()
쿼리필터에 first()메서드 (객체에 속하는 함수)를 통해, 첫 번째 일치하는 레코드를 반환한다.
generate_password_hash
:
사용자 비밀번호를 해시로 변환하여 저장할 때 사용된다. 해싱은 원래 비밀번호를 불가능하게 되돌릴 수 있는 암호화된 문자열로 변환하는 프로세스이다. 이것은 비밀번호를 안전하게 저장하고 인증 과정에서 검증하는 데 사용된다.
여기서 해싱알고리즘이 사용되는데,
method='sha256
이 바로 그것이다. 이 알고리즘은 입력 데이터를 고정 길이의 해시 값으로 변환하는 데 사용되며, 같은 입력에 대해 항상 동일한 해시를 출력합니다. 이 알고리즘은 일반적으로 권장되는 것 중 하나이다.
check_password_hash
:
저장된 해시와 사용자가 제출한 비밀번호를 비교하여 인증을 수행할 때 사용된다. 즉, 사용자가 로그인 시도할 때 입력한 비밀번호를 검증하는 데 사용된다.
사용자가 입력한 비밀번호에 대한 동일한 해시 함수를 사용하여, 해싱한 후 저장된 해시와 비교하는 과정을 거친다.
<!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>
<!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>
<!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 프로젝트에 추가할 기능이기 때문이다. 이제 회원가입을 하고나서 게시판을 만들어보자!