본문 바로가기

Javscript

[Javascript] 인증과 인가, 암호화, jwt (flask, bcrypt)

반응형

 

 

회원가입과 로그인은 알면 알수록 짜릿하다.

 

 

실생활에서 겁나 아무생각 없이 쓰고 있는 별거 아닌 기능인 거 같지만

 

사용자의 정보가 들어가기 때문에 매우매우 중요한거 같다.

 

그래서 이번에 제대로 알아보려한다.

 

 

 

 

관련 문서를 보고 이해를 바탕으로 정리하였습니다. 틀릴 수도 있으니 지적해주시면 겸허히 받아들이도록 하겠습니다.

 

 

회원가입과 로그인을 알아보기 전에 인증과 인가에 대해 먼저 알아보자

 

 

 

 


 

1. 인증 Authentication

 

인증은 사용자가 누구인지 확인하는 절차다. 회원가입과 로그인이 대표적인 예이다.

 

인증의 보안을 강화하기 위해서 내가 한 방법은

 

1. 회원가입시 bcrypt로 사용자 정보 암호화하기

2. 로그인 시 회원가입된 비번을 복호화해서 확인하는 것이 아닌 로그인 시 비밀번호를 암호화해서 확인하기

3. 로그인시 jwt(JSON Web Token)를 사용하여 access token을 통해 정보(사용자 아이디) 전달하기

4. 보안을 극대화하기 위해 refresh token 사용

정도이다.

 

 

2. 인가 Authorization

 

유저가 요청한 것을 실행할 수 있는 권한이 요청한 유저가 가지고 있는 지 확인한다.

예를 들어, 비회원은 못들어가는 페이지는 메인페이지로 리다이렉트 시켜주는 기능 또는 관리자 페이지 기능 등이 있다.

 

로그인시 전달받은 jwt를 복호화하여 이 유저가 해당 요청을 할 권한이 있는 지 확인한다.

 

 

 

이제 코드로 알아보자


 

1. 인증

 

  •  회원가입시 bcrypt로 암호화 하기

 

from pymongo import MongoClient
from flask import Flask, render_template, jsonify, request
from flask_bcrypt import Bcrypt, generate_password_hash, check_password_hash

bcrypt = Bcrypt(app)
client = MongoClient('localhost', 27017)
db = client.dbborder
# JWT
jwt = JWTManager(app)

@app.route('/user/signup', methods=['POST'])
def signup():
    # id, password 받아오고 저장
    user_id = request.form['user_id']
    user_pwd = request.form['user_pwd']
    pw_hash = generate_password_hash(user_pwd, 10)
    # id 중복확인
    if db.user.count_documents({'user_id': user_id}) == 0:
        db.user.insert_one({'user_id': user_id, 'user_pwd': pw_hash})
        return jsonify({'result': 'SUCCESS', 'message': 'SIGN UP SUCCESS'})
    else:
        return jsonify({'result': 'FAIL', 'message': 'user_id already exists'})

 

  •  로그인 시 회원가입된 비번을 복호화해서 확인하는 것이 아닌 로그인 시 비밀번호를 암호화해서 확인하기

 

@app.route('/user/login', methods=['POST'])
def login():
    # id, password 받아오귀
    user_id = request.form['user_id']
    user_pwd = request.form['user_pwd']
    # id 확인
    if db.user.count_documents({'user_id': user_id}) == 0:
        # 401 error : 인증 자격 없음
        return jsonify({'result': 'FAIL', 'message': 'WRONG ID'}) #, 401
    # else:
        # 비번 확인
        # count_documents VS findone?
    check_pwd = db.user.find_one({"user_id":user_id})
    // 로그인 시 입력한 암호를 암호화해서 저장된 비밀번호와 비교하기
    if check_password_hash(check_pwd.get('user_pwd'), user_pwd):
        response = jsonify({'result': 'SUCCESS', 'message': 'LOGIN SUCCESS'})
        access_token = create_access_token(identity=user_id)
        # refresh_token = create_refresh_token(identity=user_id)
        # session['logged_in'] = True
        set_access_cookies(response, access_token)
        return response
    else :
        return jsonify({'result': 'FAIL', 'message': 'WRONG PWD'})#, 401
        # if db.board.count_documents({'user_id': user_id, 'user_pwd': user_pwd}) == 0:
        #     return jsonify({'result': 'FAIL', 'message': 'WRONG PWD'})
        # else:
        #     return jsonify({'result': 'SUCCESS', 'message': 'LOGIN SUCCESS'})

 

  • jwt를 사용하여 정보 전달하기  Access Token + Refresh Token

 

from datetime import datetime
from datetime import timedelta
from datetime import timezone
//...
from flask_jwt_extended import create_access_token
from flask_jwt_extended import get_jwt
from flask_jwt_extended import get_jwt_identity
from flask_jwt_extended import jwt_required
from flask_jwt_extended import JWTManager
from flask_jwt_extended import set_access_cookies
from flask_jwt_extended import unset_jwt_cookies
from flask_jwt_extended import create_refresh_token

app.config["JWT_COOKIE_SECURE"] = False
app.config["JWT_TOKEN_LOCATION"] = ["cookies"]
app.config["JWT_SECRET_KEY"] = "hyuk-is-coding..."
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
//...

# Using an `after_request` callback, we refresh any token that is within
# 30 minutes of expiring. Change the timedeltas to match the needs of your application.
@app.after_request
def refresh_expiring_jwts(response):
    try:
        exp_timestamp = get_jwt()["exp"]
        now = datetime.now(timezone.utc)
        target_timestamp = datetime.timestamp(now + timedelta(minutes=30))
        if target_timestamp > exp_timestamp:
            access_token = create_access_token(identity=get_jwt_identity())
            set_access_cookies(response, access_token)
        return response
    except (RuntimeError, KeyError):
        # Case where there is not a valid JWT. Just return the original respone
        return response
        
# Protect a route with jwt_required, which will kick out requests
# without a valid JWT present.
@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
    # Access the identity of the current user with get_jwt_identity
    current_user = get_jwt_identity()
    # current_user 값이 db에 있는지 확인이 아니라
    # 200이냐 401이냐확인
    # get_jwt_identity()메서드는 현재 유효한 토큰임을 확인했기 때문에 서명된 사용자 이름을
    # 찾을 수 있을 것이다. 그 사용자 이름. 즉, 식별자 identity를 반환하는 함수다.
    return jsonify(logged_in_as=current_user), 200
    
//...

 

access token은 1시간, refresh token은 30분 후에 만료되게 구현하였다.

 

위에 login route 코드 중

access_token = create_access_token(identity=user_id)

 

를 통해, access_token에 user_id를 암호화 해서 넣어 줬다.

 

그리고

 

필요할 때 /protected route처럼

 

@app.route("/protected", methods=["GET"])
@jwt_required()
def protected():
    # Access the identity of the current user with get_jwt_identity
    current_user = get_jwt_identity()
    # current_user 값이 db에 있는지 확인이 아니라
    # 200이냐 401이냐확인
    # get_jwt_identity()메서드는 현재 유효한 토큰임을 확인했기 때문에 서명된 사용자 이름을
    # 찾을 수 있을 것이다. 그 사용자 이름. 즉, 식별자 identity를 반환하는 함수다.
    return jsonify(logged_in_as=current_user), 200

 

를 통해 정보를 받아 올 수 있다.

 

 

 

 

이것들을 이용하여 나는

 

로그인 하지 않았을 때 -> 로그인 + 회원가입 버튼

 

로그인 했을 때 -> 로그아웃 버튼

 

이 나오도록 구현했다.

 

didmount.js


function status() {
  // jwt 토큰 만료했는지 확인하고
  let userState = "";
  $.ajax({
    type: "GET",
    url: "/protected",
    async: false,
    data: {},
    success: function (response) {
      // response
      // console.log(response);
      userState = response.logged_in_as;
    },
  });
  // let userState = sessionStorage.getItem("userId");
  console.log(userState);
  if (userState === "") {
    // 로그인 화면 + 회원가입
    logoutBtn.classList.add("hidden");
    loginBtn.classList.remove("hidden");
  } else {
    // 로그 아웃
    logoutBtn.classList.remove("hidden");
    loginBtn.classList.add("hidden");
  }
}

status();

 

 

출력화면

 

로그인 안했을 시

 

로그인 시

 

 

 

잘 나온다!

 

 

오늘도 열코 ~

 


참고 사이트

https://flask-jwt-extended.readthedocs.io/

 

Flask-JWT-Extended’s Documentation — flask-jwt-extended 4.3.1 documentation

 

flask-jwt-extended.readthedocs.io

 

반응형