Sparta/스파르타코딩 웹개발 종합반

종합반 4주차 - Bulma, 로그인, 회원가입 기능

또롱또 2022. 3. 12. 00:07
728x90

필요 패키지: Flask (서버), pymongo (DB), pyJWT(토큰생성- 로그인 후 사이트에 일정시간동안 로그인이 지속되게하는거 같은 자유이용권 같은 토큰)

파이참 기준 - 세팅에 TEMPLATE LANGUAGE 검색해서 jinja2 선택

 

Bulma - Bulma는 순수한 CSS 프레임워크

https://bulma.io/documentation/

 

Bulma: Free, open source, and modern CSS framework based on Flexbox

Bulma is a free, open source CSS framework based on Flexbox and built with Sass. It's 100% responsive, fully modular, and available for free.

bulma.io

 

회원가입시 필요한 정보

 - 해시함수 - 해시함수 SHA256은 어떤 길이의 입력값을 넣어도 항상 256바이트의 결과값이 나옴

- JWT - JSON Web Token의 줄임말로, JSON 객체를 사용해 정보를 안정성 있게 전달하는 웹표준

 

 

 

.toggleClass 

            // 선택한 요소에 클래스(Class) 값을 넣었다 뺐다 할 수 있습니다.        
        function toggle_sign_up() {
                $("#sign-up-box").toggleClass("is-hidden")
                $("#div-sign-in-or-up").toggleClass("is-hidden")
                $("#btn-check-dup").toggleClass("is-hidden")
                $("#help-id").toggleClass("is-hidden")
                $("#help-password").toggleClass("is-hidden")
                $("#help-password2").toggleClass("is-hidden")
            }

 

***회원가입

- 정규표현식

// 정규표현식, 아이디 & 비밀번호
function is_nickname(asValue) {
    var regExp = /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{2,10}$/;
    // (?=.*[a-zA-Z]) - a-z 혹은 A-Z가 꼭 있어야하고
    // [-a-zA-Z0-9_.] - 나머지는 a-z, A-Z, 0-9, _, . 까지 있어도 되고
    // {2,10} 최소 2자리에서 최대 10자리 까지 이다.
    return regExp.test(asValue);
}

function is_password(asValue) {
    var regExp = /^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z!@#$%^&*]{8,20}$/;
    return regExp.test(asValue);
}

- 아이디 중복체크 (클라이언트)

function check_dup() {
	// input 값 받기
    let username = $("#input-username").val()
    console.log(username)
    // input 값이 아무것도 없을경우
    if (username == "") {
        $("#help-id").text("아이디를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
        $("#input-username").focus()
        return;
    }
    // input값을 위에서 만든 정규표현식 id에 넣었는데 원하는 아이디 형식이 아닐경우
    if (!is_nickname(username)) {
        $("#help-id").text("아이디의 형식을 확인해주세요. 영문과 숫자, 일부 특수문자(._-) 사용 가능. 2-10자 길이").removeClass("is-safe").addClass("is-danger")
        $("#input-username").focus()
        return;
    }
    
    // 그 외에 아이디 생성이 가능할때
    $("#help-id").addClass("is-loading")
    $.ajax({
        type: "POST",
        url: "/sign_up/check_dup",
        data: {
            username_give: username
        },
        success: function (response) { // db에 있는 아이디 일 경우
			// 아이디가 이미 db에 있는지 체크
            if (response["exists"]) {
                $("#help-id").text("이미 존재하는 아이디입니다.").removeClass("is-safe").addClass("is-danger")
                $("#input-username").focus()
            } else { // 없는경우, .addClass("is-success")를 추가해서, 회원가입전에 중복확인이 성공적인지 체크가 가능
                $("#help-id").text("사용할 수 있는 아이디입니다.").removeClass("is-danger").addClass("is-success")
            }
            $("#help-id").removeClass("is-loading")

        }
    });
}

- 아이디 중복체크 (서버,DB)

@app.route('/sign_up/check_dup', methods=['POST'])
def check_dup():
    # id 중복확인
    username_receive = request.form['username_give'] #request form으로 username을 받고
    # bool 값을 사용해서, db에 username이 있는지 체크
    exists = bool(db.users.find_one({"username": username_receive}))
    # bool 값을 넘겨줘서 있다 없다를 체크 가능
    return jsonify({'result': 'success', 'exists': exists})

- 회원가입 기능 (클라이언트)

function sign_up() {
	// input 값 받아오기
    let username = $("#input-username").val()
    let password = $("#input-password").val()
    let password2 = $("#input-password2").val()
    console.log(username, password, password2)
	
	// 아이디의 형식, 중복체크 검사
    if ($("#help-id").hasClass("is-danger")) {
        alert("아이디를 다시 확인해주세요.")
        return;
    } else if (!$("#help-id").hasClass("is-success")) {
        alert("아이디 중복확인을 해주세요.")
        return;
    }
	
    // 비밀번호 검사
    // 비밀번호가 빈칸인지
    if (password == "") {
        $("#help-password").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
        $("#input-password").focus()
        return;
    } else if (!is_password(password)) { //비밀번호가 정규식을 통과했는지
        $("#help-password").text("비밀번호의 형식을 확인해주세요. 영문과 숫자 필수 포함, 특수문자(!@#$%^&*) 사용가능 8-20자").removeClass("is-safe").addClass("is-danger")
        $("#input-password").focus()
        return
    } else {
        $("#help-password").text("사용할 수 있는 비밀번호입니다.").removeClass("is-danger").addClass("is-success")
    }
    if (password2 == "") {
        $("#help-password2").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
        $("#input-password2").focus()
        return;
    } else if (password2 != password) {
        $("#help-password2").text("비밀번호가 일치하지 않습니다.").removeClass("is-safe").addClass("is-danger")
        $("#input-password2").focus()
        return;
    } else {
        $("#help-password2").text("비밀번호가 일치합니다.").removeClass("is-danger").addClass("is-success")
    }
    
    // 위의 모든 검사를 통과하면 서버에 회원가입 양식을 보냄
    $.ajax({
        type: "POST",
        url: "/sign_up/save",
        data: {
            username_give: username,
            password_give: password
        },
        // 서버로부터 회원가입 ok 라는 답변을 받고, 다음 페이지로 넘어감
        success: function (response) {
            alert("회원가입을 축하드립니다!")
            window.location.replace("/login")
        }
    });

}

- 회원가입 기능 (서버)

@app.route('/sign_up/save', methods=['POST'])
def sign_up():
    # 회원가입
    # 아이디 비밀번호 받아옴
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']
    # 비밀번호에는 sha256이라는 해시함수 이용해서 암호화
    password_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    # db에 회원가입 정보를 넘겨줌
    doc = {
        "username": username_receive,                               # 아이디
        "password": password_hash,                                  # 비밀번호
        "profile_name": username_receive,                           # 프로필 이름 기본값은 아이디
        "profile_pic": "",                                          # 프로필 사진 파일 이름
        "profile_pic_real": "profile_pics/profile_placeholder.png", # 프로필 사진 기본 이미지
        "profile_info": ""                                          # 프로필 한 마디
    }
    db.users.insert_one(doc)
    return jsonify({'result': 'success'})

 

- 로그인 기능 (클라이언트)

    function sign_in() {
    			// id와 비밀번호 input 가져오기
                let username = $("#input-username").val()
                let password = $("#input-password").val()

				// id 와 비밀번호가 빈값인지 검사. 
                if (username == "") {
                    $("#help-id-login").text("아이디를 입력해주세요.")
                    $("#input-username").focus()
                    return;
                } else {
                    $("#help-id-login").text("")
                }

                if (password == "") {
                    $("#help-password-login").text("비밀번호를 입력해주세요.")
                    $("#input-password").focus()
                    return;
                } else {
                    $("#help-password-login").text("")
                }
                
                // 검사를 통과했을경우, 서버에 아이디와 비밀번호를 보내줌
                $.ajax({
                    type: "POST",
                    url: "/sign_in",
                    data: {
                        username_give: username,
                        password_give: password
                    },
                    // 아이디와 비밀번호가 성공적으로 있을경우, 유저에게 토큰지급
                    success: function (response) {
                        if (response['result'] == 'success') {
                            $.cookie('mytoken', response['token'], {path: '/'});
                            window.location.replace("/")
                        } else {
                            alert(response['msg'])
                        }
                    }
                });
            }

- 로그인 기능 (서버)

@app.route('/sign_in', methods=['POST'])
def sign_in():
    # 로그인, 아이디와 비밀번호를 클라로부터 가져옴
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']
    
    # 회원가입할때, 비밀번호는 해쉬함수로 암호화 했기때문에
    # 로그인할때도 똑같이 암호화해서 db에서 찾아준다
    pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    result = db.users.find_one({'username': username_receive, 'password': pw_hash})

	# 만약 db 안에 있을경우 = 로그인 성공
    if result is not None:
    	# 토큰 정보입력 누구한테 얼마동안 발행할지
        payload = {
         'id': username_receive,
         'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24)  # 로그인 24시간 유지
        }
        # 토큰 발행 - SECRET_KEY 역시 암호화 한번 해주는거임
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')

        return jsonify({'result': 'success', 'token': token})
    # 찾지 못하면
    else:
        return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})

- 메인페이지가 사용자 알고있기 (서버)

@app.route('/')
def home():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.users.find_one({"username": payload["id"]})
        return render_template('index.html', user_info=user_info)
    except jwt.ExpiredSignatureError:
        return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
    except jwt.exceptions.DecodeError:
        return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))

- 포스팅 (클라이언트)

function post() {
    let comment = $("#textarea-post").val()
    let today = new Date().toISOString()
    $.ajax({
        type: "POST",
        url: "/posting",
        data: {
            comment_give: comment,
            date_give: today
        },
        success: function (response) {
        	// 포스팅에 성공하면 모달의 active 클래스를 지워준다
            $("#modal-post").removeClass("is-active")
						window.location.reload()
        }
    })
}

- 포스팅 (서버)

@app.route('/posting', methods=['POST'])
def posting():
	# 토큰정보는 언제든지 쿠키를 사용해서 가져올수 있음
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        # 토큰에서 가져온것으로 페이로드를 생성하고, 그 안에 id 를 통해 유저정보를 알수있다.
        user_info = db.users.find_one({"username": payload["id"]})
        # 추가적으로 코멘트, 작성날짜를 받아와서 
        comment_receive = request.form["comment_give"]
        date_receive = request.form["date_give"]
        # db에 넣어줌
        doc = {
            "username": user_info["username"],
            "profile_name": user_info["profile_name"],
            "profile_pic_real": user_info["profile_pic_real"],
            "comment": comment_receive,
            "date": date_receive
        }
        db.posts.insert_one(doc)
        return jsonify({"result": "success", 'msg': '포스팅 성공'})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))
728x90