1) RDB와 Flask 상호작용
RDB은 간단하게 설명하자면 관계형 데이터베이스로 NoSQL과는 다르게 정형화된 데이터베이스이다.
키와 값들의 간단한 관계를 테이블화 시킨 데이터베이스이다.
- 파이썬은 오픈소스와 상용 데이터베이스에 대한 대부분의 데이터베이스 엔진을 위한 패키지를 가지고 있다.
- RDB와 Flask의 상호작용을 통해 Flask에서 입력받은 내용을 DB에 저장할 수 있다.
상호작용을 그림으로 표현한 것이다.
클라이언트가 서버에 데이터를 요청하면 flask는 데이터베이스에 그 데이터가 있는지 확인하고 처리한다.
2) 간단한 게시판 만들기
DB에 저장되는 데이터를 활용하여 사용자를 검색해보기
DB에 저장되는 데이터를 활용해서 사용자를 추가해보기 (중복 사용자 방지)
게시판 내용을 생성, 조회, 수정, 삭제해보기
- 게시판의 기능을 DB에 적용하기 위해서는 리소스를 다뤄야한다.
- RDB를 사용해서 구현하니까 Create, Read, Update, Delete를 이용하면 된다.
** 아래에 작성된 코드는 HTML 파일이 존재하고 있다는 가정하에 작성된 것 **
(1) 사용자 정보 검색
from flask import Flask, render_template, request, url_for, redirect
import sqlite3 # DB는 sqlite3을 이용
app = Flask(__name__) # 플라스크 선언
@app.route('/search', methods = ['GET', 'POST'])
def search():
if request.method == 'POST':
result = request.form['search'] # html파일에서 name이 search인 태그의 form 값을 받아옴
con = sqlite3.connect("database.db") # DB를 연결
cur = con.cursor() # cursor 객체를 생성
cur.execute(f"SELECT * FROM Board WHERE name='{result}' or context='{result}'")
# Board 테이블에서 요청받은 result가 있는지 찾는 쿼리
# f를 넣으면 중괄호 안에 변수 사용가능
rows = cur.fetchall() # 쿼리 실행 결과를 rows 변수에 저장
con.close() # DB연결 해제
print("DB:")
for i in range(len(rows)):
print(rows[i][0] + ':' + rows[i][1])
return render_template('search.html', rows = rows)
else:
return render_template('search.html')
if __name__ == '__main__':
app.run(debug=True)
result 변수에는 html 파일에서 받아온 form의 value값을 담는다.
그리고 이 result를 SQL문 안에 변수로 찾고자하는 값으로 넣어줌!!!!
db에 연결을 한 변수를 cursor() 객체로 생성해주어야 excute 함수를 사용할 수 있다.
excute()는 SQL 쿼리문을 작성하여 원하는 데이터를 찾을 수 있다.
DB에서 원하는 데이터를 불러온 뒤에는 close()를 사용하여 연결 해제를 해주어야한다.
(2) DB 사용자 추가 : name과 context 라는 값을 저장해보기
from flask import Flask, render_template, request, url_for, redirect
import sqlite3
@app.route('/add', methods = ['GET', 'POST'])
def add():
if request.method == 'POST':
try:
name = request.form['name'] # html의 form에서 name이 'name'인 입력의 값 받아옴
context = request.form['context'] # html의 form에서 name이 'context'인 입력의 값 받아옴
with sqlite3.connect("database.db") as con: # with를 사용하여 DB 연결이 가능
cur = con.cursor() # 가져온 값을 cursor 객체에 저장
cur.excute(f"INSERT INTO Board (name, context) VALUES ('{name}','{context}')")
con.commit() # commit하여 DB에 반영한다.
except:
con.rollback() # 이전 실행상태로 되돌린다.
finally :
return redirect(url_for('board')) # board url로 redirect
else:
return render_template('add.html')
if __name__ == '__main__':
app.run(debug=True)
with sqlite3.connect로 db에 연결해주면 close()과정이 따로 없어도 된다.
위 코드같은 경우는 파이썬의 try, except, finally 문을 사용하여 구현한 것이다.
입력받은 값을 SQL문을 이용하여 DB에 입력해주고 commit해 저장한다.
근데 만약에 그 과정 도중 문제가 발생하면 except: 문에서 rollback 처리 해준다.
성공하면 'board' url로 redirect!
(3) 중복 사용자 방지 - 추가하고자 하는 값이 이미 DB에 있는 값일 때
app.route('/add', methods = ['GET', 'POST'])
def add():
if request.method == 'POST':
name = request.form['name']
context = request.form['context']
with sqlite3.connect("database.db") as con:
cur = con.cursor()
# name이 DB에 있는지 확인 후 사용자를 DB에 추가 ▽
cur.execute(f"SELECT count('name') FROM Board WHERE name='{name}'") # 입력된 name이 Board 테이블에 이미 있는지 조회하는 쿼리
if cur.fetchall()[0][0] == 0: #만약 데이터가 0개라면 중복 x라는 뜻
cur.execute(f"INSERT INTO Board (name, context) VALUES ('{name}', '{context}')")
con.commit()
else: # 중복 사용자인 경우 안내 메시지를 넘긴 후 add.html을 렌더링
return render_template('add.html', msg="중복 사용자입니다. 이름을 바꿔주세요.")
return redirect(url_for('board'))
else:
return render_template('add.html')
html이 존재하고있다고 가정하고 코드를 작성했다고 했지만 마지막 (3) 예시의 html 코드를 간단히 리뷰해보면
<form action = "/add" method = "POST">
이름<br>
<input type = "text" name = "name" /><br>
내용<br>
<input type = "text" name = "context" style="text-align:center; width:400px; height:100px;"/>
</form>
action 은 /add url과 연결해주는 프로퍼티이다. 값을 추가하고자하는 거니까 당연히 POST 메서드를 사용하고
name 프로퍼티에 적힌 값을 통해 flask로 값을 받아올 수 있다. request.form['name'] 이런식~
(4) 게시판 내용 수정 및 삭제
########## 수정 #############
@app.route('/update/<uid>', methods=['GET','POST']) # <uid>는 변경할 값을 받아오는 변수이자 주소
def update(uid):
if request.method =='POST':
name = request.form['name'] # uid의 name을 바꾸고싶은 name으로 입력
context = request.form['context'] # 마찬가지
with sqlite3.connect("database.db") as con:
cur = con.cursor()
# UPDATE 문을 이용해 name과 context를 수정하는 쿼리를 실행
cur.execute(f"UPDATE Board Set name='{name}',context='{context}' WHERE name='{uid}'")
con.commit()
return redirect(url_for('board'))
else:
con = sqlite3.connect("database.db")
cur = con.cursor()
cur.execute(f"SELECT * FROM Board WHERE name ='{uid}'")
row = cur.fetchall()
return render_template('update.html',row=row)
########## 삭제 #############
@app.route('/delete/<uid>')
def delete(uid):
with sqlite3.connect("database.db") as con:
cur = con.cursor()
# DELETE 문을 이용해 데이터를 삭제하는 쿼리를 실행
cur.execute(f"DELETE FROM Board WHERE name='{uid}'")
con.commit()
return redirect(url_for('board')) # "/"로 돌아가겟다.
with 구문을 사용해 close()를 해줄 필요가 없이 구현한 것이다.
update 쿼리는 다른 쿼리사용법보다 복잡한 것 같아 살펴보고 가자면
UPDATE Board Set name='{name}',context='{context}' WHERE name='{uid}'
Board 테이블에서 name이 {uid}인 것의 name과 context를 Set을 사용하여 재설정 해준다.
3) Flask JWT
JWT는 JSON web Token의 약자로, JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Web Token이다.
토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안정하게 전달한다.
이는 웹 표준으로서 두 개체에서 JSON 객체를 사용하여 통신한다.
- JWT의 구성
Header: 토큰의 타입과 알고리즘 저장
Payload: 토큰에 담을 정보를 저장
Signature: 헤더와 정보의 인코딩 값들과 관련된 비밀키 저장
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImVsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.KeQ_ZRON0VcycCQ6YtOIoNfKPvUjn8gNlR6HdE_xsis
이런 식으로 생겼다고 한다ㄷ ㄷ.. 중간에 점(빨간색)을 구분자로 사용하며 순서대로 Header, Payload, Signature인데
JSON 형태인 각 부분은 Base64로 인코딩되어 표현된다.
- Header
헤더는 typ와 alg 두 가지 정보로 구성된다.
- typ: 토큰의 타입 지정
- alg: 알고리즘 방식을 지정 (서명 및 토큰 검증에 사용)
EX) { "typ": "JWT", "alg": "HS256" }
- Payload
토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨있다.
- 등록된 클레임: 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터
- 공개 클레임: 충돌이 방지된 이름을 가지고 있어야 한다. 이를 위해 이름을 URI 형식으로 짓는다.
URL이 아니라 URI다!!! EX) { https://hayan.io: true }
- 비공개 클레임: 서버와 클라이언트 사이에 임의로 지장한 정보를 저장 (사용자 정의 클레임)
- Signature
토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다.
서명은, 헤더의 인코딩 값과 정보의 인코딩 값을 합친 후 주어진 비밀키로 해시를 하여 생성한다.
(1) JWT를 이용한 로그인 토큰 발급 구현
from flask import Flask, request, render_template, jsonify
import jwt # jwt 모듈을 import
app = Flask(__name__)
encryption_secret = "secret" # 인코드와 디코드에 사용되는 변수
algorithm = "HS256" # 인코드와 디코드에 사용되는 변수
origin = {"name":"hayan", "password":"1234"}
@app.route("/", methods=["GET","POST"])
def jwt_route():
# 조건문을 이용해 API 요청을 구분
method = request.method
if method == 'POST':
id = request.form['username']
pw = request.form['password']
# origin에 저장된 name, password와 비교
if origin['name'] == id and origin['password'] == pw:
# 정보가 일치하는 경우 사용자 변수를 만들기 위한 딕셔너리
data_to_encode = {'name':id, 'password':pw}
# 인증이 완료되면 전송할 encode, decode 정보 저장
encoded = jwt.encode(data_to_encode, encryption_secret, algorithm=algorithm).decode
decoded = jwt.decode(encoded, encryption_secret, algorithm =[algorithm]) # decoded의 알고리즘은 리스트에
# 저장한 정보를 json 형태로 전송
data = {"encode":encoded, "decode":decoded}
return jsonify(data)
else:
return jsonify("User Not Found")
else:
return render_template("index.html")
if __name__ == "__main__":
app.run(debug = True)
'Backend > Flask' 카테고리의 다른 글
Flask 기초 - REST API (2) (Ajax를 사용한 update, delete) (0) | 2022.02.23 |
---|---|
Flask 기초 - REST API (1) (create, read) (0) | 2022.02.23 |
Flask 기초 I - SQL Alchemy (0) | 2022.02.16 |
Flask 기초 I - Blueprint, Jinja2, 로그인 구현, 로깅 (0) | 2022.02.07 |
flask 기초 I - 데이터 화면에 표현, GET, POST 등 (0) | 2022.02.04 |