6.1 사용자 인증 및 권한 부여
이번 단계에서는 멀티 컨테이너 애플리케이션의 보안 및 접근 관리를 어떻게 보장할 수 있는지 살펴볼 거야. 여기에는 사용자 인증, 데이터 암호화, API 보호 및 서비스 간 안전한 연결 설정이 포함돼.
목적: 등록되고 인증된 사용자만 애플리케이션과 상호작용하고 작업을 수행할 수 있도록 보장.
JWT (JSON Web Token) 인증 구현
1단계. 필요한 라이브러리 설치:
pip install Flask-JWT-Extended
2단계. Flask 애플리케이션에서 JWT 설정:
backend/app/__init__.py 파일에 다음의 변경사항을 추가해:
from flask_jwt_extended import JWTManager
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://taskuser:taskpassword@database:5432/taskdb'
app.config['JWT_SECRET_KEY'] = 'your_jwt_secret_key' # 너만의 비밀 키로 대체해
db = SQLAlchemy(app)
jwt = JWTManager(app)
from app import routes
3단계. 등록 및 인증을 위한 라우트 만들기:
backend/app/routes.py 파일에 다음 라우트를 추가해:
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
from werkzeug.security import generate_password_hash, check_password_hash
from app.models import User, Task
@app.route('/register', methods=['POST'])
def register():
data = request.get_json()
hashed_password = generate_password_hash(data['password'], method='sha256')
new_user = User(username=data['username'], password=hashed_password)
db.session.add(new_user)
db.session.commit()
return jsonify({'message': '사용자 등록 성공'}), 201
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(username=data['username']).first()
if not user or not check_password_hash(user.password, data['password']):
return jsonify({'message': '잘못된 인증 정보'}), 401
access_token = create_access_token(identity=user.id)
return jsonify({'access_token': access_token}), 200
@app.route('/tasks', methods=['GET'])
@jwt_required()
def get_tasks():
current_user_id = get_jwt_identity()
tasks = Task.query.filter_by(owner_id=current_user_id).all()
return jsonify([task.to_dict() for task in tasks])
4단계. 비밀번호 저장을 위한 User 모델 업데이트:
backend/app/models.py 파일 업데이트:
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
tasks = db.relationship('Task', backref='owner', lazy=True)
6.2 데이터 암호화
목표: 클라이언트와 서버 간 데이터 전송 시 보호를 제공한다.
HTTPS 사용
1단계. HTTPS 지원하는 리버스 프록시로 Nginx 설정:
프로젝트 루트 디렉토리에 nginx.conf 파일을 만든다:
server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api {
proxy_pass http://backend:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
2단계. Nginx를 위한 Dockerfile 생성:
nginx 디렉토리에 Dockerfile 파일을 만든다:
FROM nginx:latest
COPY nginx.conf /etc/nginx/nginx.conf
3단계. compose.yaml에 Nginx 추가:
Nginx 서비스를 추가하면서 compose.yaml 파일을 업데이트한다:
version: '3'
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
networks:
- task-network
backend:
build: ./backend
ports:
- "5000:5000"
depends_on:
- database
networks:
- task-network
environment:
- DATABASE_URL=postgresql://taskuser:taskpassword@database:5432/taskdb
database:
image: postgres:13
environment:
- POSTGRES_DB=taskdb
- POSTGRES_USER=taskuser
- POSTGRES_PASSWORD=taskpassword
networks:
- task-network
volumes:
- db-data:/var/lib/postgresql/data
nginx:
build: ./nginx
ports:
- "80:80"
depends_on:
- frontend
- backend
networks:
- task-network
networks:
task-network:
driver: bridge
volumes:
db-data:
6.3 Let's Encrypt로 SSL 인증서 받기
1단계. Certbot 설치:
Certbot 공식 웹사이트의 지침을 따라 Certbot을 설치해봐.
2단계. 인증서 받기:
sudo certbot certonly --standalone -d your_domain.com
3단계. Nginx SSL 설정하기:
nginx.conf를 업데이트해서 SSL을 사용 가능하게 만들어줘:
server {
listen 80;
server_name your_domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name your_domain.com;
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api {
proxy_pass http://backend:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4단계. 인증서 복사를 위해 Nginx용 Dockerfile 업데이트:
FROM nginx:latest
COPY nginx.conf /etc/nginx/nginx.conf
COPY /etc/letsencrypt /etc/letsencrypt
6.4 API 보호
목표: API 접근을 제한하고 무단 요청을 방지하기.
JWT를 사용한 라우트 보호
이미 @jwt_required() 데코레이터를 사용하여 작업 라우트를 보호했어. 이 데코레이터로 모든 중요한 라우트를 보호했는지 확인해:
from flask_jwt_extended import jwt_required, get_jwt_identity
@app.route('/tasks', methods=['GET'])
@jwt_required()
def get_tasks():
current_user_id = get_jwt_identity()
tasks = Task.query.filter_by(owner_id=current_user_id).all()
return jsonify([task.to_dict() for task in tasks])
데이터베이스 접근 제한
목표: 데이터베이스에 대한 무단 접근 방지하기.
역할 및 권한 설정
1단계. 제한된 권한을 가진 사용자 만들기:
CREATE USER limited_user WITH PASSWORD 'limited_password';
GRANT CONNECT ON DATABASE taskdb TO limited_user;
GRANT USAGE ON SCHEMA public TO limited_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO limited_user;
2단계. 환경 변수 DATABASE_URL 업데이트:
compose.yaml 파일에서 환경 변수 DATABASE_URL을 업데이트하기:
environment:
- DATABASE_URL=postgresql://limited_user:limited_password@database:5432/taskdb
GO TO FULL VERSION