Flask with oz

Haks.·2025년 1월 18일
0

How to use

목록 보기
12/32

📖 Flask

📝 REST Api

Representational State Transfer
request 데이터를 처리할 때 사용하는 방식

  • request.json
  • request.get_json()

📌 request.json

REST API에서 단순히 Json 데이터를 읽어오는 데 사용

  • REST API 요청 본문이 Content-Type: application/json 헤더를 포함하고 JSON 데이터가 올바르게 제공될 때만 사용할 수 있습니다.
  • JSON 데이터를 단순히 읽어오기만 할 때 적합합니다.
  • 요청 본문이 JSON 형식이 아니면 None을 반환합니다.

📌 request.get_json()

REST API에서 JSON 데이터를 수동으로 파싱하고 더 많은 제어를 제공

  • 요청이 JSON 형식이 아니어도 force=True를 통해 강제로 파싱할 수 있습니다.
  • JSON 파싱 실패 시 에러를 발생시키거나(silent=False), None을 반환하게 할 수 있습니다.

📝 flask-smorest

flask 애플리케이션을 위해 REST API 를 간편하게 생성하고, OpenAPI 문서를 자동으로 생성할 수 있도록 도와주는 라이브러리
OPENAPI 스펙을 사용하여 API문서화를 지원하며, Swagger UI를 통해 API 문서를 시각적으로 제공합니다.
API 작업과 관련된 블루프린트와 데코레이터 기반 방식을 활용하여 직관적이고 유지보수하기 쉬운 코드를 작성할 수 있게 합니다.

📝 Blueprint

블루프린트는 애플리케이션의 특정 기능 별로 라우팅, 뷰 함수, 템플릿, 정적 파일 등의 관리가 가능

  • API가 복잡해질 수록 관리의 필요성이 증가함.
  • 모듈화: 블루프린트를 사용하면 애플리케이션의 서로 다른 부분을 별도의 모듈로 나누어 관리할 수 있습니다. 이는 코드의 재사용성을 높이고, 유지보수를 용이하게 합니다.
  • 라우팅 관리: 블루프린트는 자체 URL 규칙을 가지고 있으며, 이를 통해 애플리케이션의 라우팅을 체계적으로 관리할 수 있습니다.
  • 기능별 분리: 블루프린트를 사용하면 특정 기능에 대한 라우팅, 뷰 함수, 에러 핸들러, 템플릿 등을 그룹화할 수 있습니다.
    라우트를 그룹화하고 각기 다른 파일이나 모듈로 분리할 수 있도록 도와줌

🧑‍💻 사용 예

from flask import Flask,Blueprint,render_template,request

my_blueprint = Blueprint('my_project','myproject', description = 'Operation blueprint', url_prefix='/blueprint')

# app.py
app.register_blueprint(my_blueprint)

📝 Abort

  • abort 함수는 API 개발 과정에서 오류 처리를 위해 사용
  • abort를 사용하여 클라이언트에 오류 상태와 메시지를 전달 가능

abort 함수는 flask_smorest에서 가져올 수 있으며, 주로 HTTP 상태 코드와 메시지를 인자로 받습니다.

from flask_smorest import abort

# 오류 상황에서 abort 호출
abort(404, message="Resource not found")
  1. HTTP 상태 코드 전송:
    • abort를 사용하면 함수에서 바로 HTTP 상태 코드를 지정할 수 있습니다. 반면에 jsonify와 같은 방법을 사용하면 상태 코드를 별도로 설정해야 합니다.
  2. 의미 있는 오류 메시지 전달:
    • abort를 사용하면 오류 메시지를 description 매개변수를 통해 쉽게 전달할 수 있습니다. jsonify를 사용하면 응답 본문에 메시지를 추가해야 합니다.
  3. RESTful API 표준화:
    • RESTful API에서는 특정한 상태 코드와 오류 메시지의 표준을 정하는 것이 중요합니다. abort를 사용하면 이러한 표준을 쉽게 따를 수 있습니다.
  4. 가독성과 유지보수성:
    • abort를 사용하면 오류 처리 부분이 더 간결하고 가독성이 높아집니다. 코드가 명확하게 오류 상황을 처리하고 해당 상태 코드를 클라이언트에게 반환합니다.

📝 ORM

Object Relational Mapping
객체 : 객체, 클래스, 속성의 구조
관계형 데이터베이스 : 테이블, 로우, 컬럼과 같은 구조
객체랑 테이블이랑 연결시키는것

📌 SQLAlchemy

파이썬의 관계 매핑 라이브러리
flask-SQLAlchemy 플라스크에서 ORM을 쉽게 사용할 수 있도록 도와주는 라이브러리

  • 데이터베이스의 테이블을 객체로 매핑하고, 객체 간의 관계를 데이터베이스의 외래 키 등으로 매핑하는 방식

  • 객체(Object) - Python(Flask,Django)

  • 관계형데이터베이스 - RDBMS

  • DB에 있는 데이터들을 객체처럼 사용할 수 있도록 도와준다. SQL쿼리문 없이 데이터 CRUD가 가능

  • ORM 사용 방식 이유

    • 데이터베이스 관련 코드가 간결해짐.
    • 결과 오류를 줄일 수 있음(쿼리 결과에 데해나 증명이 가능)
    • 쿼리를 쉽게 작성할 수 있음. (진입장벽이 있으마 SQL모다 훨씬 편함)
  • Flask-SQLAlchemy 설정

먼저, Flask 애플리케이션에 Flask-SQLAlchemy를 설정합니다. 이를 위해 SQLAlchemy 객체를 생성하고 Flask 앱과 연결합니다.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///yourdatabase.db' # 여기서 데이터베이스 URI 설정
db = SQLAlchemy(app)

🧑‍💻 orm 제작 실습

  1. 플라스크 생성
  2. DB 연결
  3. 블루프린트 설정(api=Api(App))까지
  4. 라우트 설정해서 실제 데이터 html 을 통해 넣는것 까지
  5. app.py 완성부터
from flask import Flask
from flask_smorest import Api
from db import db
from models import User, Board

app = Flask(__name__)
# sqlachemy 를 통해 database에 접속할 수 있는 명령어
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:##Dlwps753@localhost/oz'
# 객체가 바뀔때마다 trackking 하면 메모리에 너무 부하가 심해 왠만하면 false로 사용
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db.init_app(app) # db 선정

# blueprint 설정
app.config["API_TITLE"] = "My API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.1.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"

api = Api(app) # smorest 를 사용할수있게 flask 와 api 연결, 블루프린터 사용가능

from routes.board import board_blp # 등록!!
from routes.user import user_blp
api.register_blueprint(board_blp) # 만들엇으니 이제 등록
api.register_blueprint(user_blp)

from flask import render_template 
@app.route('/manage-boards')
def manage_boards():
    return render_template('boards.html')

@app.route('/manage-users')
def manage_users():
    return render_template('users.html')

if __name__ == "__main__" :
    with app.app_context(): # Flask 애플리케이션 컨텍스트를 수동으로 생성하고, 컨텍스트 안에서 작업을 수행할 수 있게 합니다.
        db.create_all() # 이거를 하면 models 에 만들어진 테이블들이 전부 만들어짐
        
    app.run(debug=True)
  1. db.py
from flask_sqlalchemy import SQLAlchemy

# SQLAlchemy 객체를 생성
db = SQLAlchemy()

# db 객체와 모델클래스를 같은 패일에 위치시킬수도 있음
# 이러면 모델별로 관리하기가 힘듬

# 따로하면 db.init_app(app) 을 사용해서 작업의 일관성을 유지할 수 있다.
  1. db 베이스에 넣을 db model 들 설정
  2. models.py
# Model -> Table 생성
# 게시글 - board
# 유저 - user

from db import db

# 나중에는 models 폴더안에 User.py 이런식으로 여러개를 만드는게 좋다ㅠ 
class User(db.Model):
    __tablename__ = "users"
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), nullable=False ) # Varchar 가 없어 파이썬으로 표현
    email = db.Column(db.String(100), unique=True, nullable = False)
    boards = db.relationship('Board', back_populates='author', lazy='dynamic') # author을 역참조 이것을 사용해서도 보드 테이블을 불러오지 않고 셀렉
    
    # lazy = 'dynamic' 실제로 데이터를 가져오지 않고 있다고 만 알려줌
    # 모든글을 한번에 업로드하지 않고 특정 글만 업로드
    
class Board(db.Model):
    __tablename__ = "boards"
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.String(300))
    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    # 원래는 foreignkey 로 연결하는거에 그쳤다면 ORM방식은 역참조 라는 방식이 있다
    author = db.relationship('User', back_populates='boards') 
  1. routes 폴더 안의 각각을 CURD 하는 코드 생성, board.py, user.py
from flask_smorest import Blueprint
from flask import request, jsonify
from flask.views import MethodView
from db import db
from models import Board

board_blp = Blueprint('Boards', 'boards', description='Operations on boards', url_prefix='/board')

# AIP List
# /board/
# 전체 게시글을 가져오는 API (GET)
# 게시글 작성 (POST)

# get,post,put,delete 등의 메소드를 각각 처리할 수 있는 메서드를 클래스에 정의할 수 있음
@board_blp.route('/')
class BoardList(MethodView) : # HTTP 메소드 별로 클래스를 정의할 수 잇께 해주는 기능을 제공
    def get(self):
        boards = Board.query.all() # board 테이블 안에 있는 모든 컬럼 가져옴 join 할필요가 없네?
        # 전부 가져왔으니까 하나씩 추리자
        # for board in boards:
        #     print("id:",board.id)
        #     print("title:",board.title)
        #     print("content:",board.content)
        #     print("userid:",board.user_id)
        #     print("author:",board.author) # user 니까 user 모델에서또 접근할수 있다
        #     print("author:",board.author.name)
        #     print("author:",board.author.email)
        
        # 더 간결히
        return jsonify([{"id": board.id, 
                        'title':board.title,
                        'content':board.content,
                        'user_id': board.author.id,
                        'author_name': board.author.name} 
                        for board in boards])
            
        # return 'success'
    def post(self):
        data = request.json # 유저가 보낸 데이터를 json 으로 받겠다.
        new_board = Board(title=data['title'], content=data['content'],user_id=data['user_id']) # 게시글을 만들려면 모델 자체를 불러와야함, 필요한 것들을 다적어서
        db.session.add(new_board) # 이객체를 추가해주세요
        db.session.commit()  # 커밋만 하면 뭘 커밋할건지 모름
        
        return jsonify({'msg': 'success create board'}), 201

# /board/<int:board_id>
# 하나의 게시글 불러오기 (GET)
# 특정 게시글 수정하기 (PUT)
# 특정 게시글 삭제하기 (DELETE)

@board_blp.route("/<int:board_id>")
class BoardResource(MethodView): # 하나만 만들거임, 보드가 모듈로 임포트 되있기에 board로 하면 안됨 이름
    def get(self,board_id): # 아이디를 갖고있는 상태에서 시작하기에 함수에서 받고 시작
        board = Board.query.get_or_404(board_id) # 보드아이디가 존재하면 받고 없으면 404 띄워라
        
        return jsonify({'id':board.id,
                        'title':board.title,
                        'content':board.content,
                        'author':board.author.name})
    
    def put(self, board_id): # orm 방식 우선 가져와
        board = Board.query.get_or_404(board_id)
        
        data = request.json # 유저가 json 형태로 보여주는 데이터를 담아주는거임 어떤 내용을 업데이트 시킬건지
        
        board.title = data['title'] # 갚 덮어쓰기 한거임 받아온거에
        board.content = data['content']
        
        db.session.commit() # 데이터가 바뀐것을 알려야함
        
        return jsonify({'msg' : 'Successfuly updated board data'}), 201
        
        # 포스트맨에서 put으로 바꿔서 해보자
    
    def delete(self, board_id):
        board = Board.query.get_or_404(board_id)
        
        db.session.delete(board)
        db.session.commit()

        return jsonify({"msg":"Successfully delete board data"}), 201

user.py

from flask import request, jsonify
from flask_smorest import Blueprint
from flask.views import MethodView # 데이터베이스 CRUD 방식 사용을 위해
from db import db # 데이터 베이스 조회
from models import User # 유저데이터 모델 불러오기

# 라우팅 작업
user_blp = Blueprint("Users", "users", description="Operations on users", url_prefix='/user')

# 설정한거 app.py 에서 등록하자 이제

# API LIST :
# 전체 유저 데이터 조회 (GET)
# 유저 생성 (POST)

@user_blp.route('/') # 라우트 경로 를 새로추가하면 flask 정지시켰다가 다시 실행 해야 적용됨
class UserList(MethodView):
    def get(self):
        users = User.query.all()
        return jsonify([{'id':user.id,
                        'name':user.name,
                        'email':user.email} 
                        for user in users])
    
    def post(self):
        data = request.json # 유저로부터 받자 postman
        # 포스트맨에서 post -> body -> raw
        # {"name":"python","eamil":"python@gmail.com"}
        
        new_user = User(name=data['name'], email=data['email']) # 만들고 new_user 객체에 담기
        
        db.session.add(new_user) # 새로운 유저를 추가해주세요
        db.session.commit()
        
        return jsonify({"msg":"Successfully created new user"}), 201

# 특정 유저 데이터 조회 (GET)
# 특정 유저 데이터 업데이트 (PUT)
# 특정 유저 데이터 삭제 (DELETE)

@user_blp.route("/<int:user_id>")
class UserResource(MethodView):
    def get(self, user_id):
        user = User.query.get_or_404(user_id)
        return jsonify({'name':user.name,'email':user.email})
    def put(self, user_id):
        pass
        user = User.query.get_or_404(user_id)
        data = request.json
        
        user.name = data['name']
        user.email = data['email']
        db.session.commit()
        
        return jsonify({"msg":"Successfully updated user"}), 201
        

    def delete(self, user_id):
        user = User.query.get_or_404(user_id)
        
        db.session.delete(user)
        db.session.commit()
        
        return jsonify({"msg":"Successfully delete user"}), 201
  1. 홈페이지 에서 받을 route 해놓은 mange- 설정 해놓은 html 작성

📝 flask-login

사용자 인증을 쉽게 관리할 수 있도록 도와주는 라이브러리
사용자 로그인 및 로그아웃 프로세스를 처리하고, 현재 로그인한 사용자의 정보에 접근 가능하게 해줌

🧑‍💻 실습

app.py

from flask import Flask
from flask_login import LoginManager
from models import User


app = Flask(__name__)
app.secret_key='flask-secret-key'

login_manager = LoginManager()
login_manager.init_app(app) # 초기화해 준것
login_manager.login_view = 'login'

@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id)

from routes import configure_route
configure_route(app)

if __name__ =="__main__":
    app.run(debug=True)

models.py

from flask_login import UserMixin


users = {'admin' : {'password':'qw123'}}


class User(UserMixin):
    def __init__(self, username):
        self.id = username
        
    @staticmethod
    def get(user_id):
        if user_id in users:
            return User(user_id)
        
        return None

routes.py

from flask import render_template, request, redirect, url_for, flash
from models import User, users
from flask_login import login_user, logout_user, login_required # 플라스크에서 제공하는 모듈

def configure_route(app): # 매개변수 넣은것
    @app.route('/')
    def index():
        return render_template('index.html')
    
    @app.route('/login', methods=['GET','POST'])
    def login():
        if request.method =='POST':
            username = request.form['username']
            password = request.form['password']
            
            user = User.get(username)
            
            if user and users[username]['password'] == password:
                login_user(user)
                
                return redirect(url_for('index')) # rediercet 해서 넣어줄떄 함수로 넣어주는거임 html로 주는게 아니라 착각 x
            
            else :
                flash("Invaild username or Password")
        return render_template('login.html')
    
    @app.route('/logout')
    def logout():
        logout_user()
        return redirect(url_for('index'))
    
    @app.route('/protected')
    @login_required
    def protected():
        return "<h1>Protected area</h1> <a href='/logout'>logout</a>"

templates/index.html,login.html

📝 flask jwt-extend

실제 토큰기반으로 authentication 하는 로그인 방식
JSON Web Tokens(jwt)
jwt : 사용자 인증 및 권한부여에서 널리 사용되는 방식으로 서버와 클라이언트 간의 안전한 정보 전달을 가능하게 함

🧑‍💻 실습

app.py

from flask import Flask, render_template
#pip install flask-jwt-extended
from jwt_utils import configure_jwt
from routes.user import user_bp

app = Flask(__name__)

configure_jwt(app)

app.register_blueprint(user_bp, url_prefix='/user')

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == "__main__":
    app.run(debug = True)

blocklist.py

# 블록리스트 관리 파일
BLOCKLIST = set()

def add_to_blocklist(jti):
    BLOCKLIST.add(jti)
    
def remove_from_blocklist(jti):
    BLOCKLIST.discard(jti)

jwt_utils.py

from flask_jwt_extended import JWTManager
from blocklist import BLOCKLIST
from flask import jsonify

jwt = JWTManager()

def configure_jwt(app):
    app.config["JWT_SECRET_KEY"] = "jwt-secret-key"
    jwt.init_app(app)

    # token expired time settings
    freshness_in_minutes = 1 # 탈취되면 위험하기에 좀 짧게 
    app.config["JWT_ACCESS_TOKEN_EXPIRES"] = freshness_in_minutes * 60 # 1 hour
    jwt.init_app(app)

    # claim: 청구하다
    # 추가적인 정보를 토큰에 넣고 싶을 때 사용
    @jwt.additional_claims_loader # @데코레이터
    def add_claims_to_jwt(identity):
        if identity == 1:
            return {"is_admin": True}
        return {"is_admin": False}

    # 토큰이 블록리스트에 있는지 확인하는 함수
    # 블록리스트에 있으면 해당 토큰이 유효하지 않다고 판단
    @jwt.token_in_blocklist_loader
    def check_if_token_in_blocklist(jwt_header, jwt_payload):
        # jti=jwt id
        return jwt_payload["jti"] in BLOCKLIST

    # 만료된 토큰이 사용되었을 때 실행되는 함수
    @jwt.expired_token_loader
    def expired_token_callback(jwt_header, jwt_payload):
        return jsonify({"msg": "Token expired", "error": "token_expired"}), 401

    # 유효하지 않은 토큰이 사용되었을 때 실행되는 함수
    # 토큰의 서명이나 구조가 유효하지 않을 때 실행됩니다. 주로 토큰 자체의 문제로 발생하는 경우에 해당합니다.
    @jwt.invalid_token_loader
    def invalid_token_callback(error):
        return (
            jsonify(
                {"message": "Invalid token", "error": "invalid_token"}
            ),
            401,
        )

    # 해당 토큰으로 접근 권한이 없는 경우
    @jwt.unauthorized_loader
    def missing_token_callback(error):
        return (
            jsonify(
                {
                    "description": "Access token required",
                    "error": "access_token_required",
                }
            ),
            401,
        )

    # fresh한 토큰이 필요한데 fresh하지 않은 토큰이 사용되었을 때 실행되는 함수를 정의합니다. 
    # 해당 응답을 반환하여 fresh한 토큰이 필요하다는 메시지를 전달
    # JWT_ACCESS_TOKEN_EXPIRES으로 토큰 만료 시간 조정
    @jwt.needs_fresh_token_loader
    def token_not_fresh_callback(jwt_header, jwt_payload):
        return (
            jsonify(
                {"description": "Token is not fresh.", "error": "fresh_token_required"}
            ),
            401,
        )

    # 토큰이 폐기되었을 때 실행되는 함수를
    @jwt.revoked_token_loader
    def revoked_token_callback(jwt_header, jwt_payload):
        return (
            jsonify(
                {"description": "Token has been revoked.", "error": "token_revoked"}
            ),
            401,
        )

routes/user.py

from flask import Blueprint, jsonify, request, render_template
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_required, get_jwt_identity
from models.user import User

user_bp = Blueprint('user', __name__)

# 임시 사용자 데이터
users = {
    'user1': User('1', 'user1', 'pw123'),
    'user2': User('2', 'user2', 'pw123')
}

@user_bp.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.json.get('username', None)
        password = request.json.get('password', None)

        user = users.get(username)
        if user and user.password == password:
            access_token = create_access_token(identity=username)
            refresh_token = create_refresh_token(identity=username)
            return jsonify(access_token=access_token, refresh_token=refresh_token)
        else:
            return jsonify({"msg": "Bad username or password"}), 401
    else:
        return render_template('login.html') # GET


@user_bp.route('/protected', methods=['GET'])
@jwt_required() # 인증된 유저인지 확인
def protected():
    current_user = get_jwt_identity()
    return jsonify(logged_in_as=current_user), 200

@user_bp.route('/protected_page')
def protected_page():
    return render_template('protected.html')

from flask_jwt_extended import get_jwt
from blocklist import add_to_blocklist  # 블랙리스트 관리 모듈 임포트
@user_bp.route('/logout', methods=['POST'])
@jwt_required()
def logout():
    jti = get_jwt()["jti"]
    add_to_blocklist(jti)  # jti를 블랙리스트에 추가
    return jsonify({"msg": "Successfully logged out"}), 200

models/user.py

class User:
    def __init__(self, id, username, password):
        self.id = id
        self.username = username
        self.password = password

📝 flask-migrate

Alembic 을 통해 SQLAlchemy 모델의 변경사항을 추적
데이터베이스 마이그레이션을 효과적으러 관리

  • 데이터베이스 스키마 변경사항이 있을 때마다 마이그레이션 파일을 생성하여 이러한 변경사항을 코드로 관리
  • 마이그레이션 파일들은 버전 컨트롤 시스템에 추가되어 데이터베이스 스키마의 변경 이력을 기록하는데 사용
    변경 사항에 따라 데이터베이스 스키마를 자동으로 업그레이드 또는 다운그레이드 가능
  • pip install Flask-Migrate
migrate = Migrate(app, db)
flask db init 초기설정, 현재 상태의 데이터베이스의 기록이 필요한 것
flask db init -> migrations 파일이 해당 폴더에 생성됨
flask db migrate -m "migrate 메세지"  마이그레이션 할떄 내용 담는거임
  - 현재 데이터베이스 상태와 모델 상태 비교: 마이그레이션은 데이터베이스 스키미의 현재 상태와 SQLAlchemy 모델의 현재 상태간의 차이를 검사
  - 차이가 감지되면 마이그레이션 스크립트 생성 : migrations/versions 디렉토리에 저장
  - 마이그레이션 스크립트 실행 전 검토 : 생성된 마이그레이션 스크립트는 사용자에게 먼저 검토를 받음, 사용자가 스크립트를 확인하고 승인하면, flask db upgrade 명령어를 사용하요 실제로 데이터베이스에 마이그레이션을 적용, 실제로 눈으로 반영이 되었나 확인하는 것
flask db upgrade
  - 터미널 창에서 실행시 migrations/versions 디렉토리에 있는 마이그레이션 스크립트가 실행됨, 이스크립트는 SQLAlchemy를 사용하여 데이터베이스의 스키마를 변경하고 모델의 변경 사항을 적용, 실제 데이터베이스가 업그레이드됨
  - 데이터베이스가 최신의 상태로 유지되도록 하는 것
  1. migrate = Migrate(app,db)
  2. 작성 변경사항 입력
  3. flask db init
  4. flask db migrate -m "메세지"
  5. versions 들어가서 확인
  6. flask db upgrade

📝 Session 인증

웹 애플리케이션에서 사용자의 상태를 유지하기 위해 주로 사용, 세션인증은 사용자가 로그인하면 서버 측에서 그 사용자의 상태를 기록하고, 로그아웃 하면 그상태를 제거하는 방식

  • 세션은 사용자가 접속해 있는 동안 서버에 유지되는 상태 정보

  • 단점 : 보안상의 문제

    • 쿠키에 저장되므로, 쿠키가 해킹되지 않도록 주의해야함
    • secret_key 유출의 위험이존재
    • 서버의 리소스를 많이 잡아먹음
    1. 로그인 폼을 통해 자신의 자격증명을 제출하면 서버는 이를 검증하고 세션에 사용자의 인증 정보를 저장함, 이정보는 서버의 메모리나 데이터베이스 등에 저장됨
    2. 세션 ID 사용자의 인증정보가 세션에 저장되면, 서버는 고유한 세션 ID를 생성하여 사용자의 브라우저에 쿠키형태로 보냄, 사용자는 이 세션 ID를 서버에 전송
    3. 상태유지 : 서버는 받은 세션 ID를 사용하여 사용자의 인증 상태와 관련 데이터를 조회
    4. 로그아웃, 서버는 해당사용자의 세션을 제거하고 세션 ID를 무효화
  • 세션 기간 설정 가능 로그인 되어 있는 기간

    • app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) # 7일간
    • login 부분에 추가해줘야함 , session.permanent = True
  • session celar() : 모든 데이터 삭제, 주로 로그아웃할때 삭제

🧑‍💻 실습

from flask import Flask, render_template, request, redirect, session, flash
from datetime import timedelta
# redirect 다시 메인페이지로 돌아오기위한 모듈
# flash : redirect 하기전에 뛰워주는 안내메세지 모듈

app = Flask(__name__)

# 인증
app.secret_key = 'flask-secret-key' # 실제로 배포시에는 .env or yaml 에 저장해서 배포해야함
# 이게 털리면 데이터베이스 내의 정보 다털릴 수 있음
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)

# admin user
users = {
    'john':'pw123',
    'leo':'pw123'
}

@app.route('/')
def index():
    return render_template('login.html')

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    
    # 보낸 아이디와 패스워드가 일치하면 세션 만들어줌
    if username in users and users[username] == password :
        session['username'] = username
        session.permanent = True
        
        return redirect('/secret')
    else :
        flash("Invaild username or password")
        return redirect('/')

@app.route('/secret')
def secret():
    # 한번더 확인하는 이유 
    # 로그인에 성공했떠라도, 브라우저를 종료하거나 다른 사용자가 브라우저를 열어 secret에 접속할 수 있음
    # 서버는 클라이언트가 올바르게 로그인된 상태인지 확인하기 위해 세션에 접속하는 것
    if 'username' in session :
        return render_template('secret.html')
    else :
        return redirect('/')
    
# 로그아웃 구현
@app.route('/logout')
def logout():
    session.pop('username', None)
    session.clear()
    return redirect('/')

if __name__ == '__main__':
    app.run(debug=True)

📖 Tip

  • 파이썬 쉘을 이용해 db CRUD 사용

```python
> python # 파이썬 쉘로 접속

from db import db
from app import app
from models import User

with app.app_context():
		# 코드 작성

# 코드생성
with app.app_context():
     new_user = User(name='newuser', email='newuser@example.com')
     db.session.add(new_user)
     db.session.commit()
     user = User.query.filter_by(name='newuser').first()
     print(user)

user.name # newuser
user.email # newuser@example.com

# 조회
with app.app_context():
	# 모든 User 레코드 조회
	users = User.query.all()

	for user in users:
		print(user.id, user.name, user.email)

with app.app_context():
	# 특정 조건을 만족하는 User 조회
	users = User.query.filter_by(name='coding').all()
	print(users

with app.app_context():
    user = User.query.filter_by(name='newuser').first()
    if user:
        print(f'Name: {user.name}, Email: {user.email}')
    else:
        print('User not found')
# 업데이트
with app.app_context():
    user = User.query.filter_by(name='newuser').first()
    if user:
        user.email = 'updated@example.com'
        db.session.commit()
        print('User updated')
    else:
        print('User not found')

# 삭제
with app.app_context():
    user = User.query.filter_by(name='newuser').first()
    if user:
        db.session.delete(user)
        db.session.commit()
        print('User deleted')
    else:
        print('User not found')
  • 직렬화 (Serialization)

    직렬화는 복잡한 데이터 구조(예: Python 객체)를 JSON과 같은 포맷으로 변환하는 과정입니다. 이 변환은 데이터를 API 응답으로 전송하거나, 파일로 저장할 때 유용하게 사용됩니다.

  • 역직렬화 (Deserialization)

    역직렬화는 JSON과 같은 포맷의 데이터를 복잡한 데이터 구조(예: Python 객체)로 변환하는 과정입니다. 이는 클라이언트에서 받은 데이터를 서버의 내부 데이터 모델로 변환할 때 사용됩니다.

  • API 응답에서 직렬화 사용: 서버가 데이터베이스에서 User 인스턴스를 조회한 후, 이를 JSON 형식으로 클라이언트에게 전송할 때 사용합니다.
  • 클라이언트 요청에서 역직렬화 사용: 클라이언트로부터 JSON 형식의 데이터를 받아, 이를 User 모델 인스턴스로 변환하여 데이터베이스에 저장할 때 사용합니다.

0개의 댓글

관련 채용 정보