이번 보안강화 - 로그인정보 암호화 강좌 진행중에.. 오류를 해결하지 못해서 질문 드립니다.
우선 내용은 이렇습니다.
1. werkzeug.security 설정진행중 패스워드 보안작업중에 "pass": hash_password(pass1) 로 변경후에 회원가입을 하면
- board.py-
from main import *
from flask import Blueprint
bluerprint = Blueprint("member", __name__, url_prefix="/member")
# @csrf.exempt
@bluerprint.route("/join", methods=["GET", "POST"])
def member_join():
if request.method == "POST":
name = request.form.get("name", type=str)
email = request.form.get("email", type=str)
pass1 = request.form.get("pass", type=str)
pass2 = request.form.get("pass2", type=str)
if name == "" or email == "" or pass1 == "" or pass2 == "":
flash("입력되지 않은 값이 있습니다.")
return render_template("join.html", title="회원가입")
if pass1 != pass2:
flash("비밀번호가 일치하지 않습니다.")
return render_template("join.html", title="회원가입")
members = mongo.db.members
cnt = members.count_documents({"email": email})
if cnt > 0:
flash("중복된 이메일 주소입니다.")
return render_template("join.html", title="회원가입")
current_utc_time = round(datetime.utcnow().timestamp() * 1000)
post = {
"name": name,
"email": email,
"pass": hash_password(pass1),
"joindate": current_utc_time,
"logintime": "",
"logincount": 0,
}
members.insert_one(post)
return redirect(url_for("member.member_login"))
else:
return render_template("join.html", title="회원가입")
@bluerprint.route("/login", methods=["GET", "POST"])
def member_login():
if request.method == "POST":
email = request.form.get("email")
password = request.form.get("pass")
next_url = request.form.get("next_url")
members = mongo.db.members
data = members.find_one({"email": email})
if data is None:
flash("회원 정보가 없습니다.")
return redirect(url_for("member.member_login"))
else:
if check_password(data.get("pass"), password):
session["email"] = email
session["name"] = data.get("name")
session["id"] = str(data.get("_id"))
session.permanent = True
if next_url is not None:
return redirect(next_url)
else:
return redirect(url_for("board.lists"))
else:
flash("비밀번호가 일치하지 않습니다.")
return redirect(url_for("member.member_login"))
return ""
else:
next_url = request.args.get("next_url", type=str)
if next_url is not None:
return render_template("login.html", next_url=next_url, title="회원로그인")
else:
return render_template("login.html", title="회원로그인")
@bluerprint.route("/logout")
def member_logout():
try:
del session["name"]
del session["id"]
del session["email"]
except:
pass
return redirect(url_for('member.member_login'))
----------------------------------------------------------------------------------------------------
- common.py -
from functools import wraps
from main import session, redirect, request, url_for, ALLOWED_EXTENSIONS
from string import digits, ascii_uppercase, ascii_lowercase
import random
import re
import os
from werkzeug.security import generate_password_hash, check_password_hash
def hash_password(password):
return generate_password_hash(password)
def check_password(hashed_password, user_password):
return check_password_hash(hashed_password, user_password)
def check_filename(filname):
reg = re.compile("[^A-Za-z0-9_.가-힝-]")
for s in os.path.sep, os.path.altsep:
if s:
filname = filname.replace(s, ' ')
filname = str(reg.sub('', '_'.join(filname.split()))).strip("._")
return filname
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS
def rand_generator(length=8):
char = ascii_lowercase + ascii_uppercase + digits
return "".join(random.sample(char, length))
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if session.get("id") is None or session.get("id") == "":
return redirect(url_for("member.member_login", next_url=request.url))
return f(*args, **kwargs)
return decorated_function
----------------------------------------------------------------------------------------------------
- __init__.py -
from flask import Flask
from flask import request
from flask import render_template
from flask_pymongo import PyMongo
from bson.objectid import ObjectId
from datetime import datetime, timedelta
from flask import abort
from flask import redirect
from flask import url_for
from flask import flash
from flask import session
# from flask_wtf.csrf import CSRFProtect
import math
import time
import os
app = Flask(__name__)
app.config["MONGO_URI"] = "mongodb://localhost:27017/myweb"
app.config["SECRET_KEY"] = "abcd"
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30)
mongo = PyMongo(app)
BOARD_IMAGE_PATH = "C:\\python\\images"
BOARD_ATTACH_FILE_PATH = "C:\\python\\uploads"
ALLOWED_EXTENSIONS =set(["txt", "pdf", "png", "jpg", "jpeg", "gif"])
app.config["BOARD_IMAGE_PATH"] = BOARD_IMAGE_PATH
app.config["BOARD_ATTACH_FILE_PATH"] = BOARD_ATTACH_FILE_PATH
app.config["MAX-CONTENT_LENGTH"] = 15 * 1024 * 1024
if not os.path.exists(app.config["BOARD_IMAGE_PATH"]):
os.mkdir(app.config["BOARD_IMAGE_PATH"])
if not os.path.exists(app.config["BOARD_ATTACH_FILE_PATH"]):
os.mkdir(app.config["BOARD_ATTACH_FILE_PATH"])
from .common import login_required, allowed_file, rand_generator, check_filename, hash_password, check_password
from .filter import format_datetime
from . import board
from . import member
app.register_blueprint(board.bluerprint)
app.register_blueprint(member.bluerprint)
----------------------------------------------------------------------------------------------------
-member.py-
from main import *
from flask import Blueprint
bluerprint = Blueprint("member", __name__, url_prefix="/member")
# @csrf.exempt
@bluerprint.route("/join", methods=["GET", "POST"])
def member_join():
if request.method == "POST":
name = request.form.get("name", type=str)
email = request.form.get("email", type=str)
pass1 = request.form.get("pass", type=str)
pass2 = request.form.get("pass2", type=str)
if name == "" or email == "" or pass1 == "" or pass2 == "":
flash("입력되지 않은 값이 있습니다.")
return render_template("join.html", title="회원가입")
if pass1 != pass2:
flash("비밀번호가 일치하지 않습니다.")
return render_template("join.html", title="회원가입")
members = mongo.db.members
cnt = members.count_documents({"email": email})
if cnt > 0:
flash("중복된 이메일 주소입니다.")
return render_template("join.html", title="회원가입")
current_utc_time = round(datetime.utcnow().timestamp() * 1000)
post = {
"name": name,
"email": email,
"pass": hash_password(pass1),
"joindate": current_utc_time,
"logintime": "",
"logincount": 0,
}
members.insert_one(post)
return redirect(url_for("member.member_login"))
else:
return render_template("join.html", title="회원가입")
@bluerprint.route("/login", methods=["GET", "POST"])
def member_login():
if request.method == "POST":
email = request.form.get("email")
password = request.form.get("pass")
next_url = request.form.get("next_url")
members = mongo.db.members
data = members.find_one({"email": email})
if data is None:
flash("회원 정보가 없습니다.")
return redirect(url_for("member.member_login"))
else:
if check_password(data.get("pass"), password):
session["email"] = email
session["name"] = data.get("name")
session["id"] = str(data.get("_id"))
session.permanent = True
if next_url is not None:
return redirect(next_url)
else:
return redirect(url_for("board.lists"))
else:
flash("비밀번호가 일치하지 않습니다.")
return redirect(url_for("member.member_login"))
return ""
else:
next_url = request.args.get("next_url", type=str)
if next_url is not None:
return render_template("login.html", next_url=next_url, title="회원로그인")
else:
return render_template("login.html", title="회원로그인")
@bluerprint.route("/logout")
def member_logout():
try:
del session["name"]
del session["id"]
del session["email"]
except:
pass
return redirect(url_for('member.member_login'))
----------------------------------------------------------------------------------------------------
- write.html -
{% extends "main.html" %}
{% block contents %}
<script>
$(document).ready(function() {
$("#summernote").summernote({
height: 300,
minHeight: null,
maxHeight: null,
lang: "ko-KR",
popover: {
image:[],
link:[],
air:[]
},
callbacks: {
onImageUpload: function(image) {
for(var i = 0; i < image.length; i++) {
uploadImage(image[i]);
}
}
}
});
});
function uploadImage(image) {
var data = new FormData();
data.append("image", image);
var csrf_token = "{{csrf_token()}}";
$.ajaxSetup({
beforeSend: function(x, s) {
if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)) {
x.setRequestHeader("X-CSRFToken", csrf_token)
}
}
});
$.ajax({
url: "{{url_for('board.upload_image')}}",
cache: false,
contentType: false,
processData: false,
data: data,
type:"post",
success: function(url) {
var image = $("<img>").attr("src",url).css('max-width', "900px");
$("#summernote").summernote("insertNode",image[0]);
},
error: function(data) {
console.log(data);
alert(data);
}
});
}
</script>
<script>
function checkForm() {
if ($.trim($("#title").val()) == "") {
alert("제목을 입력하세요");
$("#title").focus();
return false;
}
if ($.trim($("#summernote").val()) == "") {
alert("내용을 입력하세요");
$("#summernote").focus();
return false;
}
}
</script>
<div style="padding: 50px 50px 50px 50px;">
<form name="form" method="post" action="{{url_for('board.board_write')}}" onsubmit="return checkForm()"enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<div class="form-group">
<label for="name">작성자</label>
<input class="form-control" id="name" type="text" name="name" value="{{session['name']}}" readonly>
</div>
<div class="form-group">
<label for="title">제목</label>
<input class="form-control" type="text" name="title" id="title" placeholder="제목을 입력하세요"/>
</div>
<div class="form-group">
<label for="contents">내용</label>
<textarea rows="8" class="form-control" name="contents" id="summernote" placeholder="내용을 입력하세요"></textarea>
</div>
<div class="custom-file">
<input class="custom-file-input" id="customFile" type="file" name="attachfile">
<label class="custom-file-label" for="customFile">파일선택</label>
</div>
<div class="text-center"><input class="btn btn-primary" type="submit" value="작성하기"></div>
</form>
</div>
{% endblock %}
----------------------------------------------------------------------------------------------------
- edit.html -
{% extends "main.html" %}
{% block contents %}
<script>
$(document).ready(function() {
$("#summernote").summernote({
height: 300,
minHeight: null,
maxHeight: null,
lang: "ko-KR",
popover: {
image:[],
link:[],
air:[]
},
callbacks: {
onImageUpload: function(image) {
for(var i = 0; i < image.length; i++) {
uploadImage(image[i]);
}
}
}
});
});
function uploadImage(image) {
var data = new FormData();
data.append("image", image);
var csrf_token = "{{csrf_token()}}";
$.ajaxSetup({
beforeSend: function(x, s) {
if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)) {
x.setRequestHeader("X-CSRFToken", csrf_token)
}
}
});
$.ajax({
url: "{{url_for('board.upload_image')}}",
cache: false,
contentType: false,
processData: false,
data: data,
type:"post",
success: function(url) {
var image = $("<img>").attr("src",url).css('max-width', "900px");
$("#summernote").summernote("insertNode",image[0]);
},
error: function(data) {
console.log(data);
alert(data);
}
});
}
</script>
<div style="padding: 50px 50px 50px 50px;">
<form name="form" method="post" action="{{url_for('board.board_edit', idx=data._id)}}" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<div class="form-group">
<label for="name">작성자</label>
<input class="form-control" type="text" name="name" value="{{session['name']}}" readonly />
</div>
<div class="form-group">
<label for="title">제목</label>
<input class="form-control" type="text" name="title" value={{data.title}}>
</div>
{% if data.attachfile %}
<div class="form-check text-right">
<input type="checkbox" class="form-check-input" id="deleteoldfile" name="deleteoldfile">
<label class="form-check-label" for="deleteoldfile">첨부파일 삭제 ({{data.attachfile}})</label>
</div>
{% endif %}
<div class="form-group">
<label for="contents">내용</label>
<textarea rows="8" class="form-control" name="contents" id="summernote">{{data.contents}}</textarea>
</div>
<div class="custom-file">
<input class="custom-file-input" id="customFile" type="file" name="attachfile">
<label class="custom-file-label" for="customFile">파일선택</label>
</div>
<div class="text-center"><input class="btn btn-primary" type="submit" value="수정하기"/></div>
</form>
</div>
{% endblock %}
----------------------------------------------------------------------------------------------------
- join.html -
{% extends "main.html" %}
{% block contents %}
<script>
function checkForm() {
if ($.trim($("#name").val()) == "") {
alert("이름을 입력하세요");
$("#name").focus();
return false;
}
if ($.trim($("#email").val()) == "") {
alert("이메일을 입력하세요");
$("#email").focus();
return false;
}
if (!validateEmail($.trim($("#email").val()))) {
alert("이메일 유효성이 올바르지 않습니다.");
$("#email").focus();
return false;
}
if ($.trim($("#pass1").val()) == "") {
alert("비밀번호를 입력하세요");
$("#pass1").focus();
return false;
}
if ($.trim($("#pass2").val()) == "") {
alert("비밀번호를 입력하세요");
$("#pass2").focus();
return false;
}
if($.trim($("#pass1").val()) != $.trim($("#pass2").val())) {
alert("비밀번호가 일치하지 않습니다.");
$("#pass2").select().focus();
return false;
}
}
</script>
<div style="padding: 50px 50px 50px 50px;">
<form name="form" action="{{url_for('member.member_join')}}" method="POST" onsubmit="return checkForm()">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
<div class="form-group">
<label for="name">이름</label>
<input class="form-control" type="text" name="name" id="name">
</div>
<div class="form-group">
<label for="name">이메일</label>
<input class="form-control" type="text" name="email" id="email">
</div>
<div class="form-group">
<label for="name">비밀번호</label>
<input class="form-control" type="password" name="pass1" id="pass1">
</div>
<div class="form-group">
<label for="name">비밀번호 확인</label>
<input class="form-control" type="password" name="pass2" id="pass2">
</div>
<div class="text-center"><input class="btn btn-primary" type="submit" value="가입하기" /></div>
</form>
</div>
{% endblock %}
----------------------------------------------------------------------------------------------------
- login.html -
{% extends "main.html" %}
{% block contents %}
<script>
function checkForm() {
if ($.trim($("#email").val()) == "") {
alert("이메일을 입력하세요");
$("#email").focus();
return false;
}
if (!validateEmail($.trim($("#email").val()))) {
alert("이메일 유효성이 올바르지 않습니다.");
$("#email").focus();
return false;
}
if ($.trim($("#pass").val()) == "") {
alert("비밀번호를 입력하세요");
$("#pass").focus();
return false;
}
return true;
}
</script>
<div style="padding: 50px 50px 50px 50px;">
<form name="form" action="{{url_for('member.member_login')}}" method="POST" onsubmit="return checkForm()">
<input type="hidden" name="csrf_token" value="{{csrf_token()}}">
{% if next_url %}
<input type="hidden" name="next_url" value="{{next_url}}" />
{% endif %}
<div class="form-group">
<label for="email">이메일</label>
<input class="form-control" type="text" name="email" id="email">
</div>
<div class="form-group">
<label for="pass">비밀번호</label>
<input class="form-control" type="password" name="pass" id="pass">
</div>
<div class="text-center"><input type="submit" class="btn btn-primary" value="로그인"></div>
</form>
</div>
{% endblock %}
----------------------------------------------------------------------------------------------------
- view.html -
{% extends "main.html" %}
{% block contents %}
<div style="padding: 50px 50px 50px 50px;">
<table class="table table-bordered">
<tbody>
<tr>
<td colspan="2">{{result.title}}</td>
</tr>
<tr>
<td>{{result.name}}</td>
<td class="text-right">{{result.pubdate|formatdatetime}}</td>
</tr>
{% if result.attachfile %}
<tr>
<td>첨부파일</td>
<td><a href="{{url_for('board.board_files', filename=result.attachfile)}}">{{result.attachfile}}</a></td>
</tr>
{% endif %}
<tr>
<td colspan="2"><div style="min-height: 200px;"></div>{% autoescape false %}{{result.contents}}{% endautoescape %}</td>
</tr>
</tbody>
</table>
<a class="btn btn-primary" href="{{url_for('board.lists', page=page, search=search, keyword=keyword)}}">리스트</a>
{% if session["id"] == result.writer_id %}
<a class="btn btn-danger float-right ml-2" href="{{url_for('board.board_delete', idx=result.id)}}">글삭제</a>
<a class="btn btn-warning float-right" href="{{url_for('board.board_edit', idx=result.id)}}">글수정</a>
{% endif %}
</div>
{% endblock %}
----------------------------------------------------------------------------------------------------
남박사님 감사합니다! 문제 해결했습니다.
패스워드의 경우 pass1 = request.form.get("pass", type=str) => pass1 = request.form.get("pass1", type=str) 수정하니까 정상적으로 동작하더라구요!
그리고 오류나는 부분은
flask_wtf.csrf import CSRFProtect 말씀하신대로
내용을 추가하니까 오류가 나지 않습니다!
flask_wtf.csrf import CSRFProtect 아래 빨간줄 그어지는거는
구글링 해서 pip install --upgrade flask-wtf 로 업그레이드 하니까 해결됬습니다!
답변 주셔서 감사합니다~ 화이팅 하겠습니다!