JWT : Login

DATE : 2023/11/14(~15)

이번 POST에서는 앞에서 공부한 JWT를 이용해 Login 기능을 구현해보려고 한다.

코드를 살펴보기에 앞서, JWT가 어떤 과정으로 오고 가는 지 살펴보도록 하자!

(1) client가 로그인을 위해 username & password를 입력하면 client의 정보를 담은 request가

server로 날아가게 된다.

(2) login request를 받은 Server는 DB 내용을 토대로 사용자 정보를 확인한 후

인증이 완료된다면 JWT를 만들어 client에게 보내준다.

(3) 이후 client는 로그인을 한 상태로 request를 보낼 때,

server가 만들어 준 토큰을 함께 넣어 보낸다.

(4) Server는 client 요청에 포함된 JWT를 확인해 이상이 없는 경우,

요청을 처리한 응답을 client에게 보내준다.

이처럼 JWT는 로그인을 완료한 사용자에게 제공되는 값으로

로그인을 유지하는 데에 사용할 수 있는 session과 같은 친구라고 할 수 있다.


자~ 그럼 Login 과정에서 JWT를 만들어 사용해보자!

구현했던 Login 과정을 조금 수정해서

(1) 사용자가 login_id cookie를 가지고 있는 경우, index.php 내용 출력

(이번 post에서 login_id cookie를 JWT로 바꿔볼 것이다.)

(2) 사용자가 login_id cookie를 가지고 있지 않은 경우, login.html로 이동

이를 위한 index.php code는 다음과 같다.

<?php    
    // if($_COOKIE['login_id'] != 1) {
    //     header("location: login.html");
    //     exit;
    // }
    
    if(!isset($_COOKIE['JWT'])) { //jwt가 존재하지 않으면 로그인 페이지로 가!
        header("location: login.html");
        exit;
    }
    require_once("jwt.php"); //jwt가 존재하면 username 값만 payload에서 가져와봐
    $usr = getToken($_COOKIE['JWT'])['usr'];
?>

<!DOCTYPE html>
<html lang="en">
    ...
</html>

JWT cookie가 존재하지 않는다는 건 로그인을 하지 않았다는 의미로 login.html로 보내버린다! 😢

반대로 JWT cookie가 존재하면 로그인 한 상태임을 의미해 jwt.php file을 import 해주고

그 안에 작성된 getToken 함수를 통해 payload 중 사용자의 username 값만 가지고 와 사용한다.

가져온 username 값을 어디에 사용하느냐! 하면

위의 페이지는 로그인을 완료한 사용자에게만 제공되는 index.php 모습으로

우측 상단을 보면 로그인한 사용자의 username 값을 출력하고 있는 걸 볼 수 있다.


아무튼 다시 돌아와서 import한 파일, jwt.php가 어떻게 생겼는지 보도록 하자.

[ jwt.php ]

<?php
    require __DIR__.'/../../../vendor/autoload.php';
    use Firebase\JWT\JWT;
    use Firebase\JWT\Key;

    function createToken($username, $name, $mail) {
        $payload = [
            'exp' => time() + 15*60,
            'iat' => time(),
            'usr' => $username,
            'mail' => $mail,
            'name' => $name
        ];
        $secret_key = 'secret_key';

        $jwt = JWT::encode($payload, $secret_key, 'HS256');

        return $jwt;
    }

    function getToken($jwt) {
        $secret_key = 'secret_key';
        $payload = (array)JWT::decode($jwt, new Key($secret_key, 'HS256'));


        return $payload;
    }
?>

코드를 보면 총 2개의 함수가 구현되어 있는 걸 볼 수 있다.

createToken 함수는 사용자의 username, name, mail 정보를 전달 받아 payload에 해당 값을 넣고

JWT를 만들어주는 역할을 수행한다.

getToken 함수는 JWT를 전달 받아 토큰 내에 있는 payload 값을 추출해내는 함수이다.

index.php에서 사용한 getToken 함수가 바로 이 함수인데,

로그인에 성공한 사용자에게 보내준 토큰 내용 중 payload에 들어있는 값을 활용하고자 작성해두었다.

이제 다음 코스! createToken 함수가 쓰이는 곳으로 넘어 가보자!


[ login.php ]

<?php
    require_once("db_connect.php");
    require_once("login_func.php");
    require_once("jwt.php");

    $username = $_POST['username'];
    $password = $_POST['password'];

    if(!checkUsername($conn, $username)) {
        echo
        '<script>
            alert("존재하지 않는 Username 입니다.");
            location.href = "login.html";
        </script>';
    }

    if(!checkCredential($conn, $username, $password)) {
        echo
        '<script>
            alert("Password가 틀렸습니다");
            location.href = "login.html";
        </script>';
    } else {
        $res = getUserInfo($conn,$username);
        $jwt = createToken($username, $res[0], $res[1]);
        setcookie('JWT',$jwt, time()+60*15);
        // setcookie('username',$username, time()+60*15);
        // setcookie('login_id',1,time()+60*15);
        header("Location: index.php");
    }

    mysqli_close($conn);
?>

사용자가 JWT를 가지고 있지 않은 경우, login.html로 이동하게 되고

계정을 입력해 요청을 보내면 login.php가 동작하게 된다.

여기서 실행되는 login.php 코드를 위와 같이 작성해보았다.

전반적인 내용은 이전에 봤던 것과 크게 다르지 않기 때문에 추가된 부분을 주목해서 보도록 하자.

 else {
    $res = getUserInfo($conn,$username);
    $jwt = createToken($username, $res[0], $res[1]);
    setcookie('JWT',$jwt, time()+60*15);
    // setcookie('username',$username, time()+60*15);
    // setcookie('login_id',1,time()+60*15);
    header("Location: index.php");
}

원래는 사용자가 입력한 계정 정보로 인증 과정이 끝나게 되면

(1) username & login_id cookie가 만들어지고

(2) index.php로 이동해

(3) login_id cookie가 존재하므로 위에서 봤던 index.php page에 접근할 수 있는 형태였다.

여기서 달라진 점은

기존에 사용하던 cookie가 아닌 JWT를 발행해 이를 cookie 형태로 보내겠다는 것이다.

JWT에 넣고 싶은 값은 username, name, mail 정보로

각각 3개의 cookie를 만들어 보낼 바엔 payload 부분에 다 넣어보자! 싶었다.

그.래.서

name, mail 정보를 DB에서 가져오기 위한 getUserInfo 함수를 실행하고

function getUserInfo($conn, $username) {
    $query = "SELECT * from user where username = '$username'";
    $result = mysqli_query($conn, $query);
    $record = mysqli_fetch_array($result);

    $name = $record['name'];
    $mail = $record['mail'];
    
    return [$name, $mail];
}

결과로 로그인한 사용자의 name & mail 정보를 $res에 할당한다.

(참고로 getUserInfo 함수가 실행되는 시점은 계정 식별 & 인증이 끝난 이후의 시점이다.

따라서 query의 결과가 존재하는 지 확인하지 않았다.)

getUserInfo 함수에서는 $name, $mail 순으로 값을 반환하기 때문에

각 값의 순서를 잘 맞춰서 토큰을 생성하는 createToken 함수에게 전달해준다.

createToken 함수의 결과로 만들어진 토큰의 payload를 상상해보면

$payload = [
            'exp' => time() + 15*60,
            'iat' => time(),
            'usr' => 'hanhxx',
            'mail' => 'hh@abc.com',
            'name' => 'yunsoo'
];

위의 예시처럼토큰이 생성된 시점과 만료 시점의 정보를 포함해 총 5개의 값이 들어가 있을 것이다.

이제 발행한 토큰을 Cookie에 담아 JWT라고 이름 지어주면 끝!

마지막으로 코드가 잘 실행되는 지 확인해보도록 하자. 😎

(+) json web token 자체에도 유효 기간이 설정되어 있지만

이를 cookie로 만들었을 때 유효 기간을 설정하지 않으면, token은 이미 만료 되었는데 쓸데없이

cookie는 계속 유지되는 상태가 될 수 있으므로 cookie에도 일단 유효 기간을 설정해두었다.

Last updated