간단한 회원가입 / 로그인 / 보안HTML+CSS+JS+Flask/회원가입+로그인2022. 5. 10. 12:19
Table of Contents
설치할 라이브러리
- Flask: 서버
- pymongo, dnspython: atlas pymongo 조작
- PyJWT: jwt token 사용 - (사용 시에는 import jwt)
- datetime: jwt token생성 시 payload에 만료시간을 넣음
- hashlib: 회원가입시에 비밀번호를 암호화해서 DB에 저장함
회원가입
API
- 클라이언트로부터 아이디, 비밀번호, 닉네임을 받아서 mongoDB에 저장한다.
- 만약 DB에 중복되는 아이디가 있을시에 클라이언트에게 오류 메시지를 보낸다!
# 회원가입
@app.route('/api/signup', methods=['POST'])
def api_register():
id_receive = request.form['id_give']
pw_receive = request.form['pw_give']
nickname_receive = request.form['nickname_give']
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
# 이미 존재하는 아이디면 패스!
result = db.user.find_one({'id': id_receive})
if result is not None:
return jsonify({'result': 'fail', 'msg': '이미 존재하는 ID입니다!'})
else:
db.user.insert_one({'id': id_receive, 'pw': pw_hash, 'nick': nickname_receive})
return jsonify({'result': 'success'})
클라이언트
- 서버에 post 요청으로 사용자가 입력한 아이디, 비밀번호, 닉네임을 넘겨주고 응답이 성공적으로 이뤄졌다면 로그인 페이지로 넘어간다!
signup.html
function signup() {
$.ajax({
type: "POST",
url: "/api/signup",
data: {
id_give: $('#input-id').val(),
pw_give: $('#input-password').val(),
nickname_give: $('#input-nickname').val()
},
success: function (response) {
if (response['result'] == 'success') {
alert('회원가입이 완료되었습니다.')
window.location.href = '/login'
} else {
alert(response['msg'])
}
}
})
}
- 이미 존재하는 ID로 회원가입을 시도하면 오류 메세지를 던져준다!
로그인
- 로그인 기능을 구현하라면 jquery에서 쿠키를 사용하는 내용을 알아야한다!!
- 여기에서 쿠키에 대한 내용을 숙지하자!
API
- 클라이언트가 보낸 아이디, 패스워드가 DB 안에 있으면 jwt token을 생성해서 클라이언트에게 리턴해준다!
# 로그인
@app.route('/api/login', methods=['POST'])
def api_login():
id_receive = request.form['id_give']
pw_receive = request.form['pw_give']
# 회원가입 때와 같은 방법으로 pw를 암호화합니다.
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
# id, 암호화된pw을 가지고 해당 유저를 찾습니다.
result = db.user.find_one({'id': id_receive, 'pw': pw_hash})
# 찾으면 JWT 토큰을 만들어 발급합니다.
if result is not None:
# JWT 토큰 생성
payload = {
'id': id_receive,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')
# token을 줍니다.
return jsonify({'result': 'success', 'token': token})
# 찾지 못하면
else:
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
클라이언트
- 클라이언트에서 jquery의 cookie를 사용해서 jwt token을 저장하려면 <head> ~ </head>태그 내에 아래 내용을 포함시켜야 한다!!
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
- 서버에 사용자가 입력한 아이디, 패스워드를 넘겨서 유효하면 jwt-token을 리턴받아서 쿠키에 저장한 후에 홈 화면으로 돌아간다!
login.html
function login() {
$.ajax({
type: "POST",
url: "/api/login",
data: {
id_give: $('#input-id').val(),
pw_give: $('#input-password').val(),
},
success: function (response) {
console.log(response)
if (response['result'] == 'success') {
// 로그인에 성공하면 token을 쿠키에 저장!!!!
$.cookie('mytoken', response['token']);
alert('로그인 완료!')
window.location.href = '/'
} else {
alert(response['msg'])
}
}
})
}
- 만약 아이디나 비밀번호가 유효하지 않다면 알람을 띄워준당
보안
- 만약 로그인한 사용자만 특정 서비스를 이용하게 싶을때 jwt-token의 존재 및 유효 여부를 판단하면 된다!
API
- 쿠키에 jwt-token이 있는지, 유효한지 판단한 후 유효하다면 클라이언트에게 닉네임을 리턴해준다!
# 보안: 로그인한 사용자만 통과할 수 있는 API
@app.route('/api/isAuth', methods=['GET'])
def api_valid():
token_receive = request.cookies.get('mytoken')
try:
# token을 시크릿키로 디코딩합니다.
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
# payload 안에 id가 들어있습니다. 이 id로 유저정보를 찾습니다.
userinfo = db.user.find_one({'id': payload['id']}, {'_id': 0})
return jsonify({'result': 'success', 'nickname': userinfo['nick']})
except jwt.ExpiredSignatureError:
# 위를 실행했는데 만료시간이 지났으면 에러가 납니다.
return jsonify({'result': 'fail', 'msg': '로그인 시간이 만료되었습니다.'})
except jwt.exceptions.DecodeError:
# 로그인 정보가 없으면 에러가 납니다!
return jsonify({'result': 'fail', 'msg': '로그인 정보가 존재하지 않습니다.'})
클라이언트
- 서버에서 토큰이 유효하다고 판단하면 닉네임을 리턴받아서 알람으로 알려주고 만약, 토큰이 유효하지 않다면 로그인 페이지로 넘어간다!
index.html
function hello() {
$.ajax({
type: "GET",
url: "/api/isAuth",
data: {},
success: function (response) {
if (response['result'] == 'success') {
// 로그인한 사용자면 닉네임을 보여주는 알람을 띄움!
let nickname = response['nickname']
alert(`반갑습니다 ${nickname}님!!`)
} else {
alert(response['msg'])
window.location.href = '/login'
}
}
})
}
+++로그아웃
- 위와 같이 쿠키를 이용해서 로그인 기능을 구현했다면 클라이언트에서 단순히 쿠키를 비워주기만 하면 된다!!
function logout() {
$.removeCookie('mytoken');
}
전체 코드 (서버)
app.py (플라스크 서버)
from flask import Flask, render_template, jsonify, request, session, redirect, url_for
import jwt
import datetime
import hashlib
from pymongo import MongoClient
SECRET_KEY = 'SPARTA'
app = Flask(__name__)
client = MongoClient('mongodb+srv://duck:1234@cluster0.f9x0w.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.users
SECRET_KEY = 'SPARTA'
@app.route('/')
def home():
return render_template('index.html')
@app.route('/login')
def login():
return render_template('login.html')
@app.route('/signup')
def signup():
return render_template('signup.html')
# 회원가입
@app.route('/api/signup', methods=['POST'])
def api_register():
id_receive = request.form['id_give']
pw_receive = request.form['pw_give']
nickname_receive = request.form['nickname_give']
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
# 이미 존재하는 아이디면 패스!
result = db.user.find_one({'id': id_receive})
if result is not None:
return jsonify({'result': 'fail', 'msg': '이미 존재하는 ID입니다!'})
else:
db.user.insert_one({'id': id_receive, 'pw': pw_hash, 'nick': nickname_receive})
return jsonify({'result': 'success'})
# 로그인
@app.route('/api/login', methods=['POST'])
def api_login():
id_receive = request.form['id_give']
pw_receive = request.form['pw_give']
# 회원가입 때와 같은 방법으로 pw를 암호화합니다.
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
# id, 암호화된pw을 가지고 해당 유저를 찾습니다.
result = db.user.find_one({'id': id_receive, 'pw': pw_hash})
# 찾으면 JWT 토큰을 만들어 발급합니다.
if result is not None:
# JWT 토큰 생성
payload = {
'id': id_receive,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
# token을 줍니다.
return jsonify({'result': 'success', 'token': token})
# 찾지 못하면
else:
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
# 보안: 로그인한 사용자만 통과할 수 있는 API
@app.route('/api/isAuth', methods=['GET'])
def api_valid():
token_receive = request.cookies.get('mytoken')
try:
# token을 시크릿키로 디코딩합니다.
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
# payload 안에 id가 들어있습니다. 이 id로 유저정보를 찾습니다.
userinfo = db.user.find_one({'id': payload['id']}, {'_id': 0})
return jsonify({'result': 'success', 'nickname': userinfo['nick']})
except jwt.ExpiredSignatureError:
# 위를 실행했는데 만료시간이 지났으면 에러가 납니다.
return jsonify({'result': 'fail', 'msg': '로그인 시간이 만료되었습니다.'})
except jwt.exceptions.DecodeError:
# 로그인 정보가 없으면 에러가 납니다!
return jsonify({'result': 'fail', 'msg': '로그인 정보가 존재하지 않습니다.'})
if __name__ == '__main__':
app.run('0.0.0.0', port=5000, debug=True)
전체 코드(클라이언트)
홈 (index.html)
- 로그인 여부를 판단하는 기능
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<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://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<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>
<title>홈</title>
<link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Jua', sans-serif;
}
.wrap {
margin: auto;
width: 900px;
}
.title {
background-color: gainsboro;
padding: 20px 30px;
}
.posting-box {
display: flex;
justify-content: space-evenly;
margin: 10px auto;
width: 500px;
border: 3px solid black;
border-radius: 5px;
padding: 25px;
}
</style>
<script>
function go_signup() {
window.location.href = '/signup'
}
function go_login() {
window.location.href = '/login'
}
// 로그인에 성공해서 토큰이 있는 경우에만 인사하기!
function hello() {
$.ajax({
type: "GET",
url: "/api/isAuth",
data: {},
success: function (response) {
if (response['result'] == 'success') {
// 로그인한 사용자면 닉네임을 보여주는 알람을 띄움!
let nickname = response['nickname']
alert(`반갑습니다 ${nickname}님!!`)
} else {
alert(response['msg'])
window.location.href = '/login'
}
}
})
}
</script>
</head>
<body>
<div class="wrap">
<div class="title">
<h1 class="display-4">홈화면</h1>
<hr class="my-4">
<div class="posting-box" id="post-box">
<button type="submit" onclick="go_signup()" class="btn btn-primary">회원가입</button>
<button type="submit" onclick="go_login()" class="btn btn-primary">로그인</button>
<button type="submit" onclick="hello()" class="btn btn-primary">환영인사</button>
</div>
</div>
</div>
</body>
</html>
회원가입(signup.html)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<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://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<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>
<title>회원가입</title>
<link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Jua', sans-serif;
}
.wrap {
margin: auto;
width: 900px;
}
.title {
background-color: gainsboro;
padding: 20px 30px;
}
.form-group {
margin: 10px 0;
}
.posting-box {
margin: 10px auto 30px auto;
width: 500px;
border: 3px solid black;
border-radius: 5px;
padding: 25px;
}
</style>
<script>
function signup() {
$.ajax({
type: "POST",
url: "/api/signup",
data: {
id_give: $('#input-id').val(),
pw_give: $('#input-password').val(),
nickname_give: $('#input-nickname').val()
},
success: function (response) {
if (response['result'] == 'success') {
alert('회원가입이 완료되었습니다.')
window.location.href = '/login'
} else {
alert(response['msg'])
}
}
})
}
</script>
</head>
<body>
<div class="wrap">
<div class="title">
<h1 class="display-4">회원가입</h1>
<hr class="my-4">
<div class="posting-box" id="post-box">
<div class="form-group">
<label>ID</label>
<input type="email" class="form-control" aria-describedby="emailHelp"
id="input-id" placeholder="">
</div>
<div class="form-group">
<label>닉네임</label>
<input type="email" class="form-control" aria-describedby="emailHelp"
id="input-nickname" placeholder="">
</div>
<div class="form-group">
<label>비밀번호</label>
<input type="email" class="form-control" aria-describedby="emailHelp"
id="input-password" placeholder="">
</div>
<button type="submit" onclick="signup()" class="btn btn-primary">회원가입</button>
</div>
</div>
</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, shrink-to-fit=no">
<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://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<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>
<!--jquery cookie를 사용하기 위해-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<title>로그인</title>
<link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Jua', sans-serif;
}
.wrap {
margin: auto;
width: 900px;
}
.title {
background-color: gainsboro;
padding: 20px 30px;
}
.form-group {
margin: 10px 0;
}
.posting-box {
margin: 10px auto 30px auto;
width: 500px;
border: 3px solid black;
border-radius: 5px;
padding: 25px;
}
</style>
<script>
function login() {
$.ajax({
type: "POST",
url: "/api/login",
data: {
id_give: $('#input-id').val(),
pw_give: $('#input-password').val(),
},
success: function (response) {
console.log(response)
if (response['result'] == 'success') {
// 로그인에 성공하면 token을 쿠키에 저장!!!!
$.cookie('mytoken', response['token']);
alert('로그인 완료!')
window.location.href = '/'
} else {
alert(response['msg'])
}
}
})
}
</script>
</head>
<body>
<div class="wrap">
<div class="title">
<h1 class="display-4">로그인</h1>
<hr class="my-4">
<div class="posting-box" id="post-box">
<div class="form-group">
<label>ID</label>
<input type="email" class="form-control" aria-describedby="emailHelp"
id="input-id" placeholder="">
</div>
<div class="form-group">
<label>비밀번호</label>
<input type="email" class="form-control" aria-describedby="emailHelp"
id="input-password" placeholder="">
</div>
<button type="submit" onclick="login()" class="btn btn-primary">로그인</button>
</div>
</div>
</div>
</body>
</html>
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!