トークンベースの認証


ユーザー認証方法は次のとおりです.
サーバベースの認証と
トークンベースの認証方法があります.
トークンベースの認証方式を実現してみました.
(サーバベースの認証も実施)
ログイン時にサーバがトークンを生成し,クライアントがトークンを受信し,ログイン時に一人で使用できるapiのみを許可するプロセスをコードで簡単に記録する.
インストール済みライブラリ
Flask==2.0.2
pymongo==3.12.1
pyjwt==2.3.0
目次
  • ログイン後、サーバ上でトークンを作成し、クライアントにトークン
  • を提供する.
  • クライアントストレージトークン(jwt)の2つの方法
    (Cookie,localstorage=(1),(2)
  • に保存)
    利用可能なapiを実現するには
  • ログインが必要です
    (クライアントがCookieに保存されている場合、localstorageが保存されている場合)
    = (1), (2))
    (現時点(2)未実施)

  • #로그인시 토큰생성api
    @app.route('/api/login', methods=['POST'])
    def token_maker():
        #post방식으로 request시에만 등록
        if request.method == 'POST':
            #로그인 페이지에서 유저가 쓴 email, password받음
            email = request.form['email']
            pw = request.form['password']
    
            #유저 비밀번호 암호화
            pw_encrypt = hashlib.sha256(pw.encode('utf-8')).hexdigest()
    
            #로그인 페이지에서 유저가 쓴 email, password를 데이터베이스에서 확인
            findingResult = db.userInfo.find_one({'email': email, 'password': pw_encrypt}, {'_id': False})
    
            #데이터베이스에 유저가 쓴 email과 password가 있을시 토큰생성
            if findingResult:
                #토큰의 payload식별자는 유저정보중 중복되지 않는 email로하고 토큰만료시간은 600초로 설정
                payload = {'id': email,
                           #쿠키사용시에는 exp를 프론트에서 설정
                           'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=600)}
                token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
                #생성된 토큰과 메세지 json형식으로 클라이언트로 response
                return jsonify({'token': token, 'msg': 'success'})
            #데이터베이스에 유저가 쓴 email과 password가 없을 시 토큰생성실패
            return jsonify({'msg': 'Not available'})
    2-(1).
    //로그인 창에 이메일 비밀번호 누르면 실행되는 함수
    async function login_info() {
      const email = inputId.value;
      const pw = inputPw.value;
      const options = {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        },
        body: `email=${email}&password=${pw}`,
      };
      const response = await fetch("/api/login", options);
      const result = await response.json();
      //서버에서 로그인이 성공시
      if (result["msg"] == "success") {
        // jwt를 쿠키에 저장
        document.cookie = `mytoken=${result["token"]}`;
        alert("환영합니다!");
        // 메인페이지로 이동
        window.location.href = "/home";
    
        //서버에서 로그인 실패시
      } else {
        alert("ID와 비밀번호를 확인해주세요");
      }
    }
    2-(2).
    const inputId = document.querySelector('#input_id')
    const inputPw = document.querySelector('#input_pw')
    
    //로그인 창에 이메일 비밀번호 누르면 실행되는 함수
    async function login_info() {
        const email = inputId.value;
        const pw = inputPw.value;
        const options = {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        },
        body: `email=${email}&password=${pw}`,
        };
        const response = await fetch("/api/login", options)
        const result = await response.json();
        //서버에서 로그인이 성공시
        if (result['msg'] == 'success'){
            //jwt를 localStorage에 저장
            localStorage.setItem('jwt', result['token'])
            alert('환영합니다!');
            //localStorage에 있던 jwt 가져옴
            const token = localStorage.getItem('jwt')
            //jwt header에 포함
            const homeOptions = {
                method: "GET",
                headers: {
                    "jwt":token
                }
            };
            // 메인 페이지를 열기 위한 ajax요청
            // fetch("/home", homeOptions)
        //서버에서 로그인 실패시
        }else{
            alert('ID와 비밀번호를 확인해주세요')
        }
    
    }
    
    3-(1).
    #로그인을 해야 열리는 메인페이지
    @app.route('/home')
    def home():
        #http request의 header의 cookie를 받음
        token_receive = request.cookies.get('mytoken')
        #쿠키가 있을시 메인페이즈를 열어주고 payload에서 받은 유저의 이메일을 통해 유저식별
        try:
            payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
            user_info = db.userInfo.find_one({"email": payload['id']})
            return render_template('index.html', user_info=user_info)
        #쿠키가 없을시 로그인페이지로
        except jwt.ExpiredSignatureError:
            return redirect(url_for("login"))
        except jwt.exceptions.DecodeError:
            return redirect(url_for("login"))
    
    まず許可されていないコード:
    推測1)fetchとrender template部分があまり一致しない
    推測2)最初にタイトルを変更するgetリクエストは不可能である.
    3-(2).
    
    #로그인을 해야 열리는 메인페이지
    @app.route('/home')
    def home():
        #http request의 header의 jwt를 받음
        token_receive = request.headers.get('jwt')
        #헤더에 jwt가 있을시 메인페이즈를 열어주고 payload에서 받은 유저의 이메일을 통해 유저식별
        try:
            payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
            user_info = db.userInfo.find_one({"email": payload['id']})
            return render_template('index.html', user_info=user_info)
        #헤더에 jwt가 없을시 로그인페이지로
        except jwt.ExpiredSignatureError:
            return redirect(url_for("login"))
        except jwt.exceptions.DecodeError:
            return redirect(url_for("login"))