본문 바로가기

웹개발

게시판(flask)-회원가입, 로그인, 비밀번호 유효성 검사

이전에 nodejs로 만들었던 게시판의 기능을 flask로 다시 구현하였다.

비밀글, 이메일 인증, 게시판 카테고리 나누기는 제외하고 나머지 기능들을 같은 구조로 만들었다.

 

이전에는 CRUD부터 만들고 회원가입, 로그인 기능을 만들었지만 수정과 삭제는 본인의 글만 하게 하기 위해서

회원가입, 로그인 기능을 먼저 만들었다.

 

from flask import Flask, render_template, session, url_for, request, redirect, flash
import pymysql

app = Flask(__name__)
app.secret_key = 'sample_secret'

def connectsql():
    conn = pymysql.connect(host='localhost', user = 'root', passwd = 'vkfksshdmf0207', db = 'flasksql', charset='utf8')
    return conn

우선 프로젝트의 기본 설정부터 보면, app.secret_key를 설정하였다.

secret_key는 세션을 사용할 때 암호화를 하고, CSRF 공격으로부터 방어하고, flash 메세지를 전달하는데 사용된다.

로그인 기능을 사용하려면 세션이 필요해서 secret_key를 설정해 주었다.

db연결은 pymysql을 이용하여 하였다.

 

<회원가입>

@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        email = request.form['email']

        conn = connectsql()
        cursor = conn.cursor()
        query = "SELECT * FROM users WHERE email = %s"
        value = email
        cursor.execute(query, value)
        findemail = (cursor.fetchall())

        query = "SELECT * FROM users WHERE username = %s"
        value = username
        cursor.execute(query, value)
        findusername = (cursor.fetchall())
        
        if findemail:
            return render_template('registError.html', error = 'email')
        elif findusername:
            return render_template('registError.html', error = 'username') 
        else:
            query = "INSERT INTO users (username, password, email) values (%s, %s, %s)"
            value = (username, password, email)
            cursor.execute(query, value)
            data = cursor.fetchall()
            conn.commit()
            return redirect('/')
        cursor.close()
        conn.close()
    else:
        return render_template('signup.html')

회원가입 부분부터 보면, signup 링크에 GET 방식으로 요청되면 그냥 signup.html파일을 렌더링하고

POST 방식으로 요청되면 email, username, password를 받는다.

email과 username은 중복될 수 없게 하기 위해서 데이터베이스의 users 테이블에서 검색을 하고

만약 데이터가 있으면 에러창으로 연결되게 하였다.

email과 username이 중복되지 않았다면 INSERT 쿼리로 데이터를 users 테이블에 추가하고, root 주소로 redirect 하였다.

CREATE TABLE users (
    ->     id INT AUTO_INCREMENT PRIMARY KEY,
    ->     username VARCHAR(50) UNIQUE NOT NULL,
    ->     password CHAR(64) NOT NULL,
    ->     email VARCHAR(50) UNIQUE NOT NULL
    -> );

users 테이블은 mysql 안에서 이 명령어로 생성하였다.

 

<!DOCTYPE html>
<html lang="en">
	<head>
    	<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, maxinum-scale=1, user-scalable=no">
	
    	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.6/umd/popper.min.js"></script>
		<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
   		<title>regist page</title>
	</head>

	<body>
		<br><div class="container">
		<div class="card align-middle" style="width:100%; border-radius:20px;">
		<div class="card-title" style="margin-top:50px;">
			<h2 class="card-title text-center" style="color:#113366;">SIGN UP</h2>
		</div>
		<div class="card-body">
    	<form class="form-signin" method="POST" action="/signup"> 
            <input type="text" name="email" class="form-control" placeholder="email" required autofocus><br>
        	<input type="text" name="username" class="form-control" placeholder="username" required autofocus><br>
        	<input type="password" name="password" class="form-control" placeholder="password" required><br>
			<button class="btn btn-primary btn-block" type="submit">회원가입</button>
			<button class="btn btn-primary btn-block" type="button" onclick="location.href='/'">메인으로</button>
    	</form>
		</div>
		</div>
  </body>
</html>

signup.html은 이렇게 구성하였다. 

회원가입 버튼을 누르면 email, username, password 값이 넘어간다.

UI는 bootstrap에서 가져와서 사용했다.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maxinum-scale=1, user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title></title>
    </head>
    
<body>
    {% if error == 'username' %}
    <h1>이미 존재하는 아이디입니다</h1>
    {% else %}
    <h1>이미 존재하는 이메일입니다</h1>
    {% endif %}
</body>
</html>

registerError.html 파일이다.

아까 라우터에서 error를 넘겼는데 username이면 아이디 중복 메세지, email이면 이메일 중복 메세지가 뜨게 했다.

 

<로그인>

@app.route('/login', methods=['GET','POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        conn = connectsql()
        cursor = conn.cursor()
        query = "SELECT * FROM users WHERE username = %s AND password = %s"
        value = (username, password)
        cursor.execute(query, value)
        data = cursor.fetchall()
        cursor.close()
        conn.close()

        if data:
            session['username'] = request.form['username']
            session['password'] = request.form['password']
            return redirect(url_for('index'))
    else:
        return render_template ('login.html')

로그인은 username과 password를 받아서 users 테이블에서 조회를 한 뒤 데이터가 있으면 session을 설정해서 로그인이 되었다는 처리를 해준다.

GET 방식으로 요청되면 login.html 파일을 렌더링한다.

 

<!DOCTYPE html>
<html lang="en">
	<head>
    	<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, maxinum-scale=1, user-scalable=no">
	
    	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.6/umd/popper.min.js"></script>
		<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
   		<title>login page</title>
	</head>

	<body>
		<br><div class="container">
		<div class="card align-middle" style="width:100%; border-radius:20px;">
		<div class="card-title" style="margin-top:50px;">
			<h2 class="card-title text-center" style="color:#113366;">SIGN IN</h2>
		</div>
		<div class="card-body">
    	<form class="form-signin" method="POST" action="/login"> 
        	<input type="text" name="username" class="form-control" placeholder="username" required autofocus><br>
        	<input type="password" name="password" class="form-control" placeholder="password" required><br>
			<button class="btn btn-primary btn-block" type="submit">로그인</button>
			<button class="btn btn-primary btn-block" type="button" onclick="location.href='/signup'">회원가입</button>
    	</form>
		</div>
		</div>
  </body>
</html>

login.html 파일이다. username과 password를 넘겨준다.

UI는 마찬가지로 bootstrap에서 가져왔다.

 

<로그아웃>

@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

로그아웃은 정말 간단한데 그냥 session.pop으로 세션에서 로그인 정보를 없애면 된다.

 

<메인 페이지>

@app.route('/')
def index():
    if 'username' in session:
        username = session['username']

        return render_template('index.html', username = username)
    else:
        username = None
        return render_template('index.html', username = username )

root 링크에서 session에 username이 들어가 있지 않으면 username을 None으로 설정해서

index.html로 username 변수를 넘겨준다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>게시판</title>
</head>
<body>
    <h1>게시판</h1>
    {% if username %}
        <h2>{{username}}</h2>
        <a href="/logout" style="text-shadow: 2px 2px black;"><font color="white" size=6><strong>LOGOUT</strong></font></a>
    {% else %}
        <a href="/login" style="text-shadow: 2px 2px black;"><font color="white" size=6><strong>SIGN IN&nbsp;</strong></font></a> <font color="white" size=6>&middot;&nbsp;</font>
		<a href="/signup" style="text-shadow: 2px 2px black;"><font color="white" size=6><strong>SIGN UP</strong></font></a>
    {% endif %}

    <a href="{{ url_for('board') }}">게시판</a>
</body>
</html>

index.html에서는 넘겨 받은 username 변수가 None이면 login, signup링크를 보여주고

username에 값이 들어있다면 logout 링크와 username을 <h2> 태그로 띄운다.

 

 

<비밀번호 유효성 검사>

if not validate_password(password):
    flash("비밀번호는 최소 8자리 이상이어야 하며, 대문자, 소문자, 숫자, 특수 문자를 최소 1개 이상 포함해야 합니다.", "error")
    return render_template('signup.html', username=username, email=email)
def validate_password(password):
    if len(password) < 8:
        return False
    if not re.search("[a-z]", password):
        return False
    if not re.search("[0-9]", password):
        return False
    return True

validate_password 함수로 비밀번호 유효성 검사를 하는 기능을 만들고 signup 링크에서 입력받은 password로

검사를 해준다.

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

flash 메세지를 출력하게 하기 위해서 signup.html 파일에 이 코드를 추가해준다.