묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결클로드 코드 완벽 마스터: AI 개발 워크플로우 기초부터 실전까지
Claude Code + Supabase 보안 대응 방안 Q&A
Claude Code + Supabase 보안 대응 방안 Q&A목적: Supabase 사용 시 보안 위험에 대한 전문가 조언 요청출처: dalgom.bami Threads 글 (https://www.threads.com/@dalgom.bami/post/DOYjgEGEvOA?xmt=AQF0oN0fFrAEMy9AqIrJ1n__kxIwi7ImgsTyjVUTh2DH0g)안녕하세요, 강사님께서 가르쳐주신 Claude Code와 Supabase를 활용한 개발 워크플로우를 배워가면서 정말 큰 도움을 받고 있습니다. 특히 개발 속도 향상에 대해서는 정말 효과적이라고 느꼈습니다.다만, 최근에 한 Threads 글을 접하면서 몇 가지 우려사항이 생겼습니다. 실제로 Supabase를 써보니 생기는 보안 위험성에 대해 경험담을 이야기하는 글이었는데, 그 내용이 저희가 배운 워크플로우와 직접적으로 연결되는 부분이 있어서 전문가인 강사님의 견해를 정중히 구해봅니다.🚨 주요 우려사항 3가지Q1. RLS(Row Level Security) 설정의 위험성해당 글에서는 다음과 같은 문제를 지적했습니다:"여러 사용자가 엮여있는 복잡한 기능(예: 사용자 A가 사용자 B의 글에 좋아요) 의 경우, AI 코딩 도구는 복잡성을 피하기 위해 권한을 다 풀어버리는 경우 가 엄청 많았다." 실제 사례: - 비로그인 사용자도 다른 사람의 개인정보가 Response에 노출됨 - Network tab에서 확인 시 모든 데이터 조회 가능 - RLS가 제대로 설정되지 않아서 발생한 문제 질문:Claude Code(또는 다른 AI 도구)에게 Supabase 작업을 의뢰할 때, 복잡한 RLS 정책이 올바르게 설정되도록 강제할 수 있는 방법이 있을까요?개발 단계에서 이러한 RLS 오설정을 미리 감지할 수 있는 검증 방법(테스트, 문서 체크 등)이 있을까요?강사님께서 추천하시는 "올바른 RLS 지시사항"이 있을까요? 예를 들어, AI에게 요청할 때 구체적으로 어떻게 명시해야 보안 구멍이 최소화될까요?Q2. Migration 파일 관리와 AI의 한계"RLS를 Dashboard UI에서 설정하면 코드로 추적 불가능하고, AI가 기존 설정을 파악하기 어렵다. Migration 파일로 관리해야 AI가 기존 설정을 이해할 수 있지만, 이것도 AI가 여러 버전의 Migration 파일을 읽고 이해해야 한다는 의미다. 결국 AI는 기존 설정을 완벽히 이해할 수 없어서 충돌하는 설정을 추가할 확률이 높다." 질문:Claude Code로 Supabase 작업을 할 때, Migration 파일의 버전 관리를 어떻게 해야 AI가 기존 설정을 명확하게 인식할 수 있을까요?메모리 파일(CLAUDE.md)에 "현재 RLS 정책 요약" 또는 "Migration 히스토리 요약"을 저장해두는 것이 도움이 될까요?강사님께서 추천하시는 최선의 관리 방식은 무엇일까요?Option A: Dashboard UI는 절대 쓰지 말고 항상 Migration 파일로만 관리?Option B: Dashboard에서 변경 후, 매번 SQL Export해서 Migration으로 변환?Option C: 다른 방식?Q3. Trigger & RPC 오남용으로 인한 유지보수 악화"복잡한 기능을 구현할 때 AI는 점점 RLS, Trigger, RPC를 남발해서 유지보수가 아예 불가능할 정도의 코드, 기존 데이터가 손실될만한 코드를 무지성으로 찍어낸다." 질문:Claude Code에게 Supabase 작업을 의뢰할 때, Trigger와 RPC 사용을 적절히 제한하는 방법이 있을까요?예를 들어, AI에게 미리 "RPC는 이 3가지 경우에만 사용하되, 그 외에는 절대 쓰지 말아달라"라고 명시할 수 있는 효과적인 프롬프트가 있을까요?만약 AI가 불필요하게 Trigger/RPC를 추가했다고 판단되면, "이건 너무 복잡하니까 클라이언트에서 처리하자" 같은 명확한 지시사항이 도움이 될까요?Q4. 프로토타입 vs 실제 서비스 단계에서의 Supabase 사용"PoC(프로토타입) 단계에서는 Supabase를 적극 활용해도 좋지만, 실제 서비스가 시작되고 사용자가 생기면 일반 백엔드 스택으로 migration해야 한다. 다행히 PostgreSQL 기반이라 마이그레이션이 비교적 쉽다." 질문:강사님께서도 이 의견에 동의하시나요? 아니면 Supabase도 충분히 프로덕션 레벨의 보안을 유지할 수 있다고 생각하시나요?만약 동의하신다면, 어느 정도 규모(DAU, 데이터 량, 기능 복잡도)에서 백엔드 스택으로 마이그레이션하는 것이 현실적일까요?그 전환 지점까지 Supabase를 "안전하게" 사용하는 체크리스트가 있을까요?Q5. 개발자(Claude Code 사용자)가 반드시 알아야 할 백엔드 지식질문:강사님께서 생각하시는 "Supabase를 안전하게 쓰는 데 필수적인 최소한의 백엔드 지식"은 무엇일까요?그 지식들을 Claude Code 사용자들이 미리 학습하도록 권장하시는 방법이 있을까요? Q6. Claude Code + Supabase 안전 가이드라인 제안위의 위험성들을 고려할 때, Claude Code 사용자들이 Supabase를 안전하게 사용할 수 있도록 하는 일종의 "체계화된 가이드라인"이 필요하다고 생각합니다.제안 & 질문:강사님께서 생각하시는 "Claude Code + Supabase 안전 개발 체크리스트"는 어떤 형태일까요?예를 들어:□ 이 작업에 RLS가 필요한지 명확히 정의했는가? □ AI에게 요청할 때 RLS 정책을 구체적으로 명시했는가? □ Migration 파일로 저장했는가? □ 개발 환경에서 Network tab으로 데이터 노출 여부 확인했는가? □ Trigger/RPC 사용이 정말 필요한지 재검토했는가? 혹은 Supabase를 쓸 때만 따로 체크리스트를 가져야 할까요?강사님께서 향후 강의에서 이런 부분을 더 강조하거나 추가 자료를 제공하실 계획이 있으신가요?🙏 마무리강사님의 강의를 통해 Claude Code의 강력함을 많이 배우고 있습니다. 다만 강한 도구일수록 신중하게 사용해야 한다는 것을 배웠고, 특히 백엔드와 보안 관련해서는 더욱 그렇다고 생각합니다.다소 길고 상세한 질문들이지만, 강사님의 전문가적 관점에서의 조언을 진심으로 기다리고 있습니다.감사합니다! 🙏
-
미해결
브라우저의 SOP 정책의 실효성 질문
Same Origin Policy가 CSRF 공격을 막기 위해 필요하다고 배웠습니다. 그런데 정말 SOP가 꼭 필요한지가 의문입니다. SOP가 없더라도, 쿠키의 SameSite=Strict 옵션을 사용하고, 타 사이트 localstorage에 접근하는 것을 차단하기만 한다면, 다른 사용자의 자원을 탈취하는 것을 막을 수 있을 것 같은데요. 그 외의 자원에 대한 SOP는 어차피 브라우저 주소창에 주소를 입력하기만 하면 누구나 접근할 수 있는 데이터에 대한 접근을 막을 뿐이지 않나요?
-
해결됨면접 전에 알고 가면 좋을 것들 - 신입 Java 백엔드 개발자편
Drive by download 공격에 대해 찾아보았는데, 맞게 이해했는지 궁금합니다!
안녕하세요! "4. 운영과 보안 - XSS와 CSRF 공격을 막으려면 어떻게 해야 할까?" 강의를 보던 중에 drive by download 공격에 대해 공부해보라고 말씀해주셔서, 널널한 개발자님의 코멘트를 참고하고, LLM과 문답하며 이해한 내용을 적어보았습니다. 제가 이해한 내용이 맞는지 확인하고 싶어 질문을 남깁니다. 🙇♂ Drive-by Download는 웹사이트를 방문만 해도 사용자의 클릭이나 동의 없이 악성코드가 침투될 수 있는 공격입니다. (cf. drive by : '지나가면서 쏘는' 의미) 이 공격이 가능한 이유는 브라우저가 iframe이나 광고를 표시하기 위해 제3의 서버에서 JavaScript를 다운로드하여 실행하기 때문입니다. 특히 광고 네트워크나 외부 위젯이 해킹당하면, 정상적인 웹사이트를 통해서도 악성 코드가 전파될 수 있습니다. 과거(~2010년대 초반)에는 Flash Player 같은 플러그인의 취약점을 통해 시스템에 직접 접근이 가능했습니다.(cf. 지금은 사라진 Flash Player 플러그인이 성능을 위해 C/C++ 코드로 구현되어, 브라우저에서 JS 엔진이 아닌 플러그인 API를 통해 로드되어 실행되었습니다. 그래서 운영체제 수준의 바이너리로 동작할 수 있어, 보안상 더 위험했다고 합니다..!) 하지만 현재에는 브라우저 샌드박스 덕분에 브라우저에서 js로 할 수 있는 작업이 제한적이어서, Drive-by Download로 인한 피해가 크게 줄어들었다고 이해했습니다. 강의 내용 뿐만 아니라 강의 중에 추천해주신 기술 블로그를 보며, 시야가 넓어지는 느낌을 받았습니다.좋은 강의 감사합니다!
-
해결됨직장인에게 꼭 필요한 파이썬-아래아한글 자동화 레시피
한글 문서를 불러오기 시 매번 경고 팝업이 뜹니다.
"한글 문서를 불러오고, 저장하고, 닫기" 챕터 관련입니다. 강의 내용에 따라 한글 문서 불러오는 명령을 하면, 파일이 열리기 전에 경고 팝업이 매번 뜹니다. 혹시 제 PC나 진행한 실습에 문제가 있는 것일까요? 진행한 내용은 아래와 같습니다.(cmd 창에서 아래와 같이 진행) C:\Users\user>python>>> import win32com.client as win32>>> hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")>>> hwp.XHwpWindows.Item(0).Visible = True>>> hwp.Open("C:\\Users\\user\\Desktop\\문서1.hwpx") 여기까지 진행하면 아래와 같은 경고 팝업이 뜹니다. C:\Users\user\Desktop\문서1.hwpx 한글을 이용하여 위 파일에 적근하려는 시도(파일의 손상 또는 유출의 위험 등)가 있습니다. 정상적인 작업 과정에만 접근을 허용하십시오. 정확인 내용은 cmd 창에서 진행한 사항에 대한 스크린샷을 첨부드립니다. (위 기재한 내용과 동일합니다.) 질문이 너무 기초적인 내용일 수도 있는데, 코딩 배경지식이 없고 강의 듣기 시작한지 초반이라 쉬운 것에도 막히는 거 같습니다. 답변 부탁드립니다. 감사합니다.
-
미해결
배포 시 사기성 사이트라는 경고문을 없앨 수 없나요?
git에서 작은 토이 프로젝트를 배포했는데 이런 경고문이 나옵니다. 세부정보로 들어 갈 수는 있는데 너무 거슬려요.저런 경고문을 없앨 수는 없나요? 프로젝트를 배포할 때마다 저런게 뜰 것 같아서 걱정됩니다. 당연하지만 바이러스나 그런 건 당연히 없구요. 클론코딩으로 만든 사이트입니다.
-
미해결AWS 클라우드 서비스 인프라 구축 이해와 해킹, 보안
s3 퍼블릭 액세스 관련
안녕하세요! 강의 잘 듣고 있습니다! S3 강의 파트에서 Public Access 차단을 위한 버킷 설정(4가지) 각각의 차이에 대한 설명이 없으셔서 찾아봐도 이해가 되지 않아 질문드립니다. 새 ACL을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 임의의 ACL을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 새 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 임의의 퍼블릭 버킷 또는 액세스 지점 정책을 통해 부여된 버킷 및 객체에 대한 퍼블릭 액세스 차단 해당 옵션들이 각각 어떻게 다른건지 설명 부탁드려도 될까요?
-
미해결[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버
서버 보안 관련 질문 드립니다!
안녕하세요, 항상 강의 정말 재미있게 수강하고 있습니다! 서버의 구조가 어떤지, 어떻게 동작하는지 전반적으로 알 수 있게 되어서 루키스님께 정말 감사할 따름입니다. 강의를 듣고나서 추가적으로 궁금한 점이 생겨서 질문 드립니다! 강의에서 만든 TCP 서버(게임 서버)와 클라이언트가 패킷을 주고 받을 때 현업에서는 어떻게 보안 처리를 하는지 궁금합니다. 현재 서버 구조는 패킷 전체를 암호화한다면 패킷 헤더에 있는 내용을 읽을 수 없어서 ReadBuffer에서 몇 바이트를 읽어와야 하는지 알 수 없게 될텐데 게임 서버 구조가 이러한 구조라면 헤더는 빼고 암호화를 하여 보안처리를 해야 복호화 후에 데이터를 읽어올 수 있을 것 같아서요. 아니면 다른 방법을 사용하는 것인지 궁금합니다!
-
해결됨남박사의 파이썬으로 실전 웹사이트 만들기
(회원가입 비밀번호 일치하지 않음 및 flask_wtf.csrf 오류) 보안강화 - 로그인 정보 암호화 하고 CSRF Protect 구현하기 강의중 오류..
남박사님 안녕하세요 이번 보안강화 - 로그인정보 암호화 강좌 진행중에.. 오류를 해결하지 못해서 질문 드립니다. 우선 내용은 이렇습니다. 1. werkzeug.security 설정진행중 패스워드 보안작업중에 "pass": hash_password(pass1) 로 변경후에 회원가입을 하면 비밀번호가 일치하지 않습니다라고 계속 나오더라구요.. 몽고db에서 내용지워도 마찬가지 입니다. 그전에는 정상적으로 로그인이 되었습니다. 2. flask_wtf.csrf import CSRFProtect 를 적용하는데 pip install flask-wtf를 설치해도 오류가 나오더라구요.. 문제 사진 1번 2번 3번 4번 5번 코드 - 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 %} ----------------------------------------------------------------------------------------------------
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
파일 업로드 기능 구현 시 보안 문제는 따로 스프링 시큐리티 등을 통해 차단하는 것일까요?
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 웹서버에서 파일 업로드 기능을 구현하면, 다음과 같은 보안 취약점을 고려해야 한다는 글을 보고 궁금해서 여쭙습니다. ----------------------------------------------------------- 공격자는 첨부 기능을 이용하여 웹쉘 (Web 과 같은 악성 파일을 서버에 업로드한 뒤 웹상에서 실행하여 서버의 권한 (O/S 계정 을 획득할 수 있고 , 서버 내의 각종 정보 폴더 파일 구조 , 프로그램 소스내용 , DB 포함한 타 서버 연결정보 등 을 이용하여 다양한 악의적인 행동을 취할 수 있다. ----------------------------------------------------------- 이런 보안 취약점은 스프링 시큐리티 등을 별도로 적용하여 예방하는 것일까요? 커리큘럼 중에는 웹서버 보안 관련한 강의는 없는 것 같아서 혹시 차후 강의 계획이 있으신지도 궁금합니다!
-
미해결
게임 차단 프로그램을 만들기 위해 학습해야 할 내용을 알려주세요.
게임 차단 프로그램( ex) 맘아이 프로그램, 아이안심 프로그램 ) 의 기능을 구현하고 싶은데 어떤 강의들을 들어야하는지 몰라 커뮤니티에 올려봅니다.
-
해결됨따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
몽고디비 Postman으로 api test할 때 질문
안녕하세요 좋은 강의 잘 듣고 있습니다. Postman으로 회원 정보를 localhost:5000/register api에 test하는 부분에서 send를 누르고 200 Ok가 뜰 때 User 회원정보가 몽고디비에 insert된건지, 아니면 postman에서 임시로 테스트하는 것이므로 insert가 된 것이 아닌지 궁금합니다. 만약 유저가 insert가 이루어졌다면 몽고디비 클라우드에서 User 명단을 어디서 조회할 수 있을까요? + 추가로 github에 commit할 때 index.js에 몽고디비 비밀번호가 그대로 실릴텐데 이걸 따로 .gitignore파일에 담게할 수는 없을까요? 클러스터 비밀번호는 강의랑 똑같이 하여 지금은 상관없을 듯 한데, 나중에 개인프로젝트 같은 걸 할 때 어떻게 할지 궁금합니다 감사합니다!
-
미해결[리뉴얼] 파이썬입문과 크롤링기초 부트캠프 [파이썬, 웹, 데이터 이해 기본까지] (업데이트)
프로그램 완성 후..보안문제
선생님 안녕하세요.. 강의는 정말 잘 듣고 이를 업무에도 활용하고자 제가 셀레니움을 활용해서 프로그램을 만들었습니다. Pyinstaller 로 실행파일을 만들어 작동도 잘되고 합니다만, 사내에서 사용하려면 보안성검토를 거쳐야한다고 합니다. 제가 만든 프로그램은 셀레니움과 tkinter만을 이용하여 만든 프로그램인데 어떤 보안 취약점이 있으며..보완할 방법은 없는지 여쭈어봅니다 ㅠㅠ
-
미해결
코딩 잘 못해도 정보보안 강의 들을 수 있나요?
중 고딩 때 c언어 공부 하고 파이썬은 방과후로 들어서 막 잘하는 수준은 아닙니다 둘다. 까먹기도 했고요. 근데 이 수준으로 모의해킹 이런 강의들 소화할 수 있나요? 중 고딩 때 c언어 공부 하고 파이썬은 방과후로 들어서 막 잘하는 수준은 아닙니다 둘다. 까먹기도 했고요. 근데 이 수준으로 모의해킹 이런 강의들 소화할 수 있나요?
-
미해결
웹해킹 강의 맥북으로도 수강 가능한가요?
안녕하세요! 웹개발자와 정보보안 입문자들이 반드시 알아야 될, 웹 해킹과 보안 그리고 시큐어 코딩 수업을 들으려고하는데요, 맥북으로도 수강이 가능한건지 여쭤보고 싶습니다!
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
안녕하세요, Persistence.xml 의 데이터베이스 정보 외부공개 방지 관련하여 질문드립니다.
안녕하세요, 김영한님. 해당 강의로 다른분들이랑 스터디 진행중인 학생입니다. 스터디를 통해, 서로 강의를 보고 만든 예제를 github 에 업로드 하기로 하였는데, 저는 DB를 제 개인 DB를 사용하고 있습니다. persistence.xml 에 user 정보, password, 제 외부 ip 를 commit 하여 push 하면 다른분들한테, 제 DB정보를 공개해야만 하고, commit 에 제외시키자니 user, password, url을 제외한 다른 설정들을 공유할수가 없어서 난감한 상황입니다 😭😭 지금은 민감한 정보만 제외하고 commit, push하고 있긴 한데, 실수하면 리포지토리 자체를 밀어야 해서 불안 합니다. 혹시 어떤 방법을 통해 민감한 정보를 제외한 소스를 공유할수 있을까요?? 강의랑 벗어날수 있는 주제 질문드려서 죄송합니다.
-
미해결리액트로 나만의 블로그 만들기(MERN Stack)
http로 배포해도 괜찮을까요?
https를 시도해보려다가 실패해서 http로 배포하려고합니다. 강의하신대로 따라했고 http로 배포한다면 보안이 위험할까요??