Sparta/항해99

항해99 - 2일차 - JWT / 해시함수/ JINJA2/ Inline & Block / 백준1단계 / React Bulma / 절대경로 vs 상대경로

또롱또 2022. 5. 10. 05:44
728x90

 

오늘의 목표
플러스강의 4주차
팀공부 JWT 예제 / JInja2 Template 공부
팀작업 미니프로젝트 맡을부분 확실히 정하기, 작업시작
게시물확인.html,css 완성

회원정보 DB에 저장되면 Jinja2로 가져와서 사용
개인공부 CSS - Inline, Block 개념 정리
전산학공부 1일 1로그 100일 완성 001 ~ 003
알고리즘공부 백준 1단계 - 입출력과 사칙연산
개인작업 React - Home.js 마무리

 

해시함수 

- 항상 똑같은 길이의 암호함수를 돌려준다

- 결과값을 가지고 원래값을 유추할 수 없다.

 

PyJWT

Python에서 JWT를 사용 하기 쉽게 만든 패키지

 

 

회원가입 - 로그인 순서도로 JWT와 해시함수등 이해

 

1. 회원가입

아이디 비밀번호 정규표현식 (규칙생성)

function is_nickname(asValue) {
	// 영문,숫자, 특수문자 (_ .)으로 2-10길이
    var regExp = /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{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);
}

 

- 클라이언트에서 입력받은 id와 pw를 Server로 POST

- pw1하고 pw2 의 일치 불일치로 비밀번호 체크

function sign_up() {
                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
                    },
                    success: function (response) {
                        alert("회원가입을 축하드립니다!")
                        window.location.replace("/login")
                    }
                });

            }

 

- 클라이언트에서 보낸 id, pw를 서버에서 받음

- pw에 해시함수 SHA256을 넣어서 암호화를 해서 필요한 정보를 DB에 저장

* SHA256 - 어떤 길이의 입력값을 넣어도 항상 256바이트의 결과값을 돌려줌

@app.route('/sign_up/save', methods=['POST'])
def sign_up():
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']
    password_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    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'})

 

2. 중복확인

- 클라이언트에서 입력받은 id정보를 가지고 중복 체크

- 서버로 어떠한 결과를 보여주면 될지 요청

- 서버에서 받아온 값이 있으면 아이디 중복되는게 있음

function check_dup() {
                let username = $("#input-username").val()
                console.log(username)
                if (username == "") {
                    $("#help-id").text("아이디를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-username").focus()
                    return;
                }
                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) {

                        if (response["exists"]) {
                            $("#help-id").text("이미 존재하는 아이디입니다.").removeClass("is-safe").addClass("is-danger")
                            $("#input-username").focus()
                        } else {
                            $("#help-id").text("사용할 수 있는 아이디입니다.").removeClass("is-danger").addClass("is-success")
                        }
                        $("#help-id").removeClass("is-loading")

                    }
                });
            }

 

- 클라이언트에서 보내준 데이터로 DB에 가서 id 같은게 있는지 찾기

같은값이 있으면 exists 결과값 보냄

@app.route('/sign_up/check_dup', methods=['POST'])
def check_dup():
    username_receive = request.form['username_give']
    exists = bool(db.users.find_one({"username": username_receive}))
    # print(value_receive, type_receive, exists)
    return jsonify({'result': 'success', 'exists': exists})

 

 

3. 로그인

회원가입후 replace()를 통해 로그인 페이지로 자동이동

로그인에 사용된 id, pw 를 받아와서 Server로 보내주고

 성공여부를 기다림 -> 성공할시 토큰을 지급받는데, 

그 토큰을 쿠키에 저장해서 로컬에서 사용자를 기억할 수 있게 도와줌

 function sign_in() {
                let username = $("#input-username").val()
                let password = $("#input-password").val()

                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'])
                        }
                    }
                });
            }

 

서버에서는 받아온 id, pw로 아이디 유무체크

비밀번호는 처음에 회원가입할때 encode를해서 DB에 저장했기 때문에

찾을때 다시 encode를 해서 DB에서 찾는다.

결과값이 None이 아닐경우

id의 유저에게 '토큰'을 지급하고 지급된 '토큰'을 결과값으로 클라이언트에 보내줌.

@app.route('/sign_in', methods=['POST'])
def sign_in():
    # 로그인
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']

    pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    result = db.users.find_one({'username': username_receive, 'password': pw_hash})

    if result is not None:
        payload = {
         'id': username_receive,
         'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24)  # 로그인 24시간 유지
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')

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

 

4. 로그인 후

서버에서는 쿠키에서 토큰을 가져와서 토큰에 맞는 사용자 정보를 가져오고

user_info에 담아서 클라이언트에 보내줌 

-> 클라에서는 이 정보로 유저에 관련된 정보들을 이용할 수 있다.

@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="로그인 정보가 존재하지 않습니다."))

 

유저정보로 이용하는 예

// 진자 템플릿으로 유저의 이름을 가져온다.
<a class="image is-32x32" href="/user/{{ user_info.username }}">

 

 

Client-side rendering (CSR) vs Server-side rendering (SSR)

CSR은 브라우저에서 모든걸 처리 - 속도가 빠름, 자바스크립트 양에따라 무거워 질 수 있다, 초기 로딩시간이 길다.

SSR - 로딩이 빠름, 필요한걸 서버에 요청해서 가져와야해서 렌더링이 느림, 정적인 사이트에 좋음

 

 

Jinja2 템플릿 엔진

- 파이썬에서 동작하는 템플릿 엔진

- 파이썬에서 작성한 명령을 자바스크립트에서 받아서 사용안하고,

HTML에서 바로 연결해서 사용할 수 있게 도와줌 ----> 속도가 빨라진다.

//구분자는 총 두가지 종류이다 : jinja 템플릿 문법임을 구분하는 용도

1. {{ ... }} : 변수나 표현식의 결과를 출력하는 구분자

example in html) 변수 표현
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>

2. {% ... %} : if문이나 for문 같은 제어문을 할당하는 구분자

example in python) 반복문
{% for <개별요소> in <리스트> %}
	<실행코드>
{% endfor %}

example in python) if 문
{% if <조건> %}
    <실행코드>
{% elif <조건> %}
    <실행코드>
{% else %}
    <실행코드>
{% endif %}

 

Flask에서는 무언가를 return 할때 아래처럼 user_info라는 변수에 user_info를 담아서 보낸다.

// (앞)user_info - index.html에서 user_info라는 이름으로 받아서 사용할 예정
// (뒤)user_info - python에서 수집한다음 index.html로 보낼 데이터
return render_template('index.html', user_info=user_info)

 

Index.html에서의 사용 예제

<a class="image is-32x32" href="/user/{{ user_info.username }}">

 

Jinja2 템플릿 엔진을 이용할려면

vscode - extension에서 jinja 설치

pycharm은 그냥 이용해도 되는듯 싶다.

 

Github 에 대해서

협업할시 Branch를 만들어서 사용한다 - 나만의 workspace 같은 느낌

작업이 끝나면 내 Branch에 작업 내용을 커밋, 푸쉬하고

깃헙 레파지토리에 들어가면 이제 병합을 진행할 방법에 대해 노란박스로 떠있다.

거기서 확인을 하고 병합을 main 브랜치에 진행한 다음에

내 브랜치를 지우고, 동료들에게 main branch에 pull 할 내용이 있다고 알려주면 된다.

 

Inline vs Block

img태그가 분명히 inline일텐데 width 적용이 되는게 이상해서 찾아본 자료

비교대상 Inline  Block Inline-block
Tags example <a><br>, <span> 등 <div><form><h1-6><header><hr><li><nav><ol><p><ul> 등 <img>
Property width, height 적용 x width가 기본으로 100% width 적용 가능,
안하면 사진 기본크기적용

 

알고리즘 공부 - 백준 1단계

- 입출력, 사칙연산 -

새로 알게된점

값을 입력받기
input()

여러개의 값을 동시에 입력받기도 가능하다
x, y = input().split()

형변환
print(int(x)+int(y))

변수의 문자열을 뒤에서부터 index를 가져오기(인덱스 -1에 위치한 글자 한개)
y[-1]

변수의 문자열을 뒤에서부터 index를 가져오기(인덱스 -1에 위치한 글자 부터 뒤로 쭉)
y[-5:]

 

4주차 공부를 하던도중 Bulma 프레임워크가 React에도 있을까 검색해보니 있었다.

// 설치
npm i --save react-bulma-components
// Document 주소
https://couds.github.io/react-bulma-components/?path=/docs/elements-block--blocks

 

절대경로 vs 상대경로

절대경로: url주소

상대경로는 아래와 같다.

기호 기능
./ 현재 위치
../ 현재 기준, 상위 디렉토리 위치
./static 현재 기준, 하부 static디렉토리 위치

 

오늘 React 개인 공부중 만들어본 Home.js

그런데 Component를 만들어 진행한건 아니고 대부분 scss였었다.

그리고 뭔가 이런 전체적으로 원을 눌러놓은 모형 말고

좌우는 조금 덜 둥글둥글해서 알약같은 박스들을 만들어 보고싶었는데,

css 기술부족으로 이정도에 만족할수 밖에 없었다.

728x90