인증/인가 Token

박요셉·2024년 7월 18일

Simple note

목록 보기
7/18

인증을 할 때 여러 방식이 있는데 그 중 Token을 발행하여 인증하는 것에 대해 좀 알아보자.

토큰 기반 인증?

토큰 기반 인증이란 사용자가 자신의 아이덴티티를 확인하고 고유한 엑세스 토큰을 받을 수 있는 프로토콜을 말한다.
사용자는 토큰 유효 기간 동안 동일한 웹페이지나 앱, 혹은 그 밖에 해당 토큰으로 보호를 받는 리소스로 돌아갈 때마다 자격 증명을 다시 입력할 필요 없이 토큰이 발급된 웹사이트나 앱에 엑세스 할 수 있다.

인증 토큰은 도장이 찍힌 티켓과 같다. 토큰이 유효하다면 사용자는 계속해서 엑세스 할 수 있다.
사용자가 로그아웃하거나 앱을 종료하면 토큰도 무효화된다.

토큰 기반 인증은 비밀번호 기반 또는 서버 기반 인증 기법과는 다르다. 토큰이 두 번째 보안 계층을 제공하여 관ㄹ리자가 각 작업과의 트랜잭션?을 정밀하게 제어할 수 있다.

하지만 토큰을 사용하려면 약간의 코딩 노하우가 필요하다.

인증 토큰의 역사

인증과 인가는 서로 다른 개념이지만 연관성이 있다. 인증 토큰을 사용하기 전에는 비밀번호와 서버가 사용되었다. 기존 방식을 사용해 적합한 사람이 필요할 때 원하는 정보에 엑세스 했는지 확인해야 했지만 항상 효과적이지는 못했다.

비밀번호를 예로 들면, 일반적으로 다음과 같은 과정이 작용한다.

  • 사용자 생성 = 문자, 숫자, 기호를 조합하여 비밀번호를 생성한다.
  • 기억 = 고유한 비밀번호 조합을 항상 기억해야 한다.
  • 반복 = 엑세스가 필요할 때마다 비밀번호를 입력해야 한다.

비밀번호 역시 서버 인증이 필요하고, 사람들이 로그인할 때마다 컴퓨터가 트랜잭션 레코드?를 생성하며, 결과적으로 메모리 부하가 증가한다.

하지만 토큰 인증은 다르다. 토큰 인증 방식에서는 보조 서비스를 통해 서버 요청을 확인한다.
확인이 완료되면 서버가 토큰을 발급하여 요청에 응답한다.

사용자는 여전히 적어도 한 개의 비밀번호를 기억해야 하지만 토큰이 엑세스 계층을 하나 더 제공하기 때ㅑ문에 도용하거나 침투하기가 훨씬 더 어렵다.

또한 세션 레코드가 서버에서 공간을 전혀 차지하지 않는다.

4가지 단계의 간단한 토큰 인증 절차

토큰 기반 인증 시스템을 사용하면 방문자가 자격 증명을 한 번만 하면된다.
자격 증명이 확인되면 토큰이 할당되어 정해진 시간동안 엑세스가 가능하다.

프로세스는 다음과 같다.

  • 요청 : 사용자가 서버 또는 보호되는 리소스에 대한 엑세스를 요청한다. 이 때 비밀번호를 이용한 로그인이나 그 밖에 지정된 프로세스가 개입될 수 있다.
  • 확인 : 서버가 해당 사용자의 ㅇ렉세스 여부를 확인한다. 이 때는 사용자 이름에 대한 비밀번호 확인 또는 그 밖에 지정된 프로세스가 개입된다.
  • 토큰 : 서버가 링, 키, 휴대전화 등의 인증 디바이스와 통신한다. 확인을 마치면 서버가 토큰을 발급하여 사용자에게 전달한다.
  • 저장 : 작업이 지속되는 동안 토큰이 사용자의 브라우저에 저장된다.

사용자가 서버에서 다른 곳에 방문하려고 하면 브라우저에 저장되어 있는 토큰이 서버와 다시 통신한다.
토큰에 따라 엑세스가 허용되기도 하고 거부되기도 한다. 관리자가 토큰에 대한 사용 제한을 설정하기도 한다.
예를 들어, 사용자가 로그아웃 하거나 지정된 시간이 지나면 토큰이 자동으로 파기되도록 설정할 수 있다.

장점

  1. 서버의 부하가 적다
    토큰은 클라이언트의 정보를 클라이언트의 브라우저에서 저장을 하는 방식이다.
    서버에서 정보를 저장하여 필요할 때마다 대조하는 방식과는 다르다.

  2. 확장성이 좋다.
    클라이언트의 새로운 요청에 대하여 새로운 토큰을 헤더에 포함하여 응답하는 방식으로, 여러 서비스를 다양한 토큰으로 관리할 수 있게 된다.

  3. 어디서나 생성 가능하다.
    꼭 토큰을 검증하는 곳에서만 발급할 필요가 없다. 토큰만 발행하는 서버가 있을 수 있고, 심지어 인증을 다른 기관에 맡길 수도 있다.


JWT(JSON Web Token)

JSON의 형태로 데이터를 구성한 이후에암호화를 하여 토큰으로 발급하는 방식이다.
구조는 헤더(Header), 페이로드(Payload), 시그니처(Signature)가 header.payload.signature형식으로 쓰인다.

헤더와 페이로드 부분은 다음과 같이 JSON의 형식으로 먼저 준비가 된다.

// header
{
  "alg":"HS256",
  "typ":"JWT"
}

// payload
{
  "userId" : "JejuAlrock",
  "name" : "Gafiled",
  "email" : "JejuAlrock@velog.io"  
}

이 두 부분을 base64의 문자열 형태로 인코딩한다.
시그니처는 인고딩 된 헤더와 페이로드 부분을 합쳐 salt값과 함께 헤더에서 지정한 암호화방식으로 암호화한 값이다.
이 세 부분을 이어 붙이면 토큰이 완성된다.
base64로 인고딩 된 부분은 다음과 같이 쉽게 디코딩된다. 그러니 민감한 정보는 페이로드에 담지 않는게 좋다.

토큰의 사용처는 발급 이후의 이용자의 신원이 유지되고 있을 때이다.
권한이 필요한 작업을 클라이언트가 요청을 할 때 비로소 시그니처를 사용하게 된다.
시그니처를 서버에서 공유하는 salt 값으로 디코딩이 되어야 이 이용자가 당사자가 맞다는 것을 서버에서 검증할 수 있게 된다.

문제점

여전히 문제접은 존재한다.

결국 쿠키를 사용하는 방식이기 때문에 똑같이 탈취의 위험이 존재한다.
토큰을 탈취해 다른 기기에서 신원인증을 시도하면 똑같이 유효하게 된다.
이 때문에 보통 토큰은 두 가지로 발행한다.
AccessTokenRefreshToken이다.
AccessToken은 비굣적 생명주기가 짧지만 주요 인증을 할 수 있는 수단으로 사용된다.
RefreshToken은 전자보다 생명주기가 길지만 전자의 재발급용으로 사용되어진다.
결국 두 가지 모두 탈취되면 문제가 발생하는 것은 마찬가지다.
이 때문에 보안이 중요한 서비스에서는 아예 RefreshToken을 발행하지 않고 AccessToken의 생명을 매우 짧게 제공하는 방식으로 사용한다.'


서버 Cors 설정

const corsConfig = {
    credentials : true,
    origin : ["http://Samehost"],
    method : ["GET", "POST", "OPTIONS"],
}

쿠키를 이용하기에 credentials는 true로

//login controller
const Users = require("../db/Users");
const crypto = require("crypto")
const jwt = require("jsonwebtoken");

const signin = async (req, res)=>{
  		// 비밀번호 암호화
  		// 간단히만 구현한 것이기 때문에 사용에 주의해라.
        const hash = crypto.createHash("sha256");

        const userId = req.body.userId;
        const plainPassword = req.body.userPassword;
        const hashedPassword = await hash.update(plainPassword).digest("hex");
  		
  		// 데이터베이스 암호 갖고오기
        const targetPassword = await Users.findOne({
            attributes : ["userId", "password"],
            where : {userId}
        })
        .then(res=>res.get("password"))
        .catch(err=>err);

        if (targetPassword && hashedPassword === targetPassword){
          	// AccessToken 생성
            const accessToken = jwt.sign(userId, process.env.ACCESSTOKEN_SECRET);
          
          	// 쿠키 생성 및 설정
            res.cookie("accessToken", accessToken, 
                       {
                          httpOnly: true, 
                          secure: true, 
                          samesite:"none", 
                          maxAge : 60* 3600 * 1000,
                       });
            return res.status(200).send({msg : "OK"});
        } else {
            return res.status(401).send({msg : "Wrong input for id or password."});
        }
    }

사실 AccessToken을 만드는 것 자체는 어렵지 않다. 이는 JsonWebToken 라이브러리가 다 제공해주기 때문이다. 여기서는 페이로드로 사용자ID 만 넣어주었다.
특히 주의할 점은 cookie의 설정이다. httponly, secure, samesite 살펴보자. httponly는 javasciprt를 통한 접근이 불가능하게 만든다. secure는 https의 환경에서만 전송되게하여 HTTP 전송 중의 탈취를 막는다. samesite는 또한 쿠키 전송을 제한한다. "none"의 설정은 사실 아무 곳이나 전송되어도 된다는 의미다. 이번 구현에서는 구현 편의를 위해서 이렇게 지정을 했지만 보안을 위해서라면 lax나 strict를 이용하기 바란다.
이 절차가 끝난 이후 클라이언트는 AccessToken을 가지고 필요할 때 권한을 부여받아 서비스를 받을 수 있게 된다.

profile
개발자 지망생

0개의 댓글