Token-based Authentication

Vorhandenheit ·2022년 1월 16일
0

JS/Node 

목록 보기
27/63

토큰 기반 인증

토큰 기반 인증방식에 대해 살펴보기 전에 서버 기반 인증방식에 대해 살펴볼려고 합니다.

서버 기반 인증방식

서버 기반 인증방식 일 경우에는 서버 측에서 유저들의 정보를 기억하고 있어야했습니다.

저번 시간에 세션은 서버측에 저장한 다는 것에 대해서 살펴봤습니다. 유저의 수가 엄청 많다면 이 세션도 엄청 많아집니다. 이는 곧 서버해야할 처리 용량이 늘어나는 것을 의미합니다. 처리해야할 용량이 늘어나니, 분산 시스템을 설계해야하는데 설계를 하는 과정에 비용, 처리 과정에서 시간이 추가적으로 걸립니다.

이러한 문제점에 대한 대안으로 토큰 기반 인증이 나오게되었습니다

1.토큰 기반 인증

토큰 기반 인증방식에는 claim기반 토큰과 랜덤 값 기반 토큰이 있습니다.

  • Claim 기반 토큰: JWT와 같이 공개돼도 문제가 없는 유저의 정보를 담은 토큰을 말합니다.
  • 랜덤 값 기반 토큰: OAuth에 의해 발급되는 access_token과 같이 랜덤한 스트링이 들어있는 토큰을 애기합니다.

2. 토큰 기반 특징

(1) Stateless, Scalability (무상태, 확장성)

토큰은 client에 저장하기 때문에 stateless하며, 서버를 확장하기에 적합한 환경을 제공합니다. 만약에 세션을 서버측에 저장했고 서버를 여러대 사용하여 요청을 분선하였다면, 어떤 유저가 로그인 했을 때, 그 유저는 처음 로그인했던 서버에만 요청을 보내야만 설정을 해야합니다.

(2) 보안성

클라이언트가 서버에 요청을 보낼 때, 더 이상 쿠키를 전달하지 않음으로 쿠키를 사용함으로 인해 발생하는 취약점이 사라집니다.

(3) Extensibility(확장성)

Scalability는 서버를 확장하는 걸 의미하는 반면, Extensibility는 로그인 정보가 사용되는 분야를 확장하는 걸 의미합니다. 토큰을 사용하여 다른 서비스에서도 권한을 공유할 수 있습니다.

3. JWT (JSON Web Token)

Json 포맷을 이용해서 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token입니다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달합니다.

(1) JWT 구조

JWT는 Header, Payload, Signature의 3부분으로 이루어지며, Json 형태인 각 부분은 Base64로 인코딩되어 표현됩니다. 또한 각각의 부분을 이어주기 위해 . 구분자를 사용하여 구분합니다.

어떤 종류의 토큰인지, 어떤 알고리즘으로 sign 할지가 적혀있습니다.

{
	"alg": "H256",
    "typ": "JWT"
}
  • type : 토큰의 타입을 지정합니다.(JWT)
  • alg : 해싱 알고리즘을 지정합니다(보통 HMAXC SHA256/RSA 사용합니다)

Payload

어떤 정보에 접근 가능한지에 대한 권한을 담을 수도 있고, 사용자의 유저 이름등 필요한 데이터는 이곳에 담아 sign 시킵니다.
토큰의 페이로드에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨져 있습니다.
클레임은 총 3가지로 나누어지며, Json(Key/Value) 형태로 넣을 수 있습니다.

{
	"sub": "someInformation",
    "name": "phillip",
    "iat": 151623391
}
  • 예시
{
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
}
A. Registered Claim

등록된 클레임은 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터들로, 모두 선택적으로 작성이 가능하며 사용할 것을 권장합니다. 또한 JWT를 간결하게 하기 위해 key는 모두 길이 3의 String입니다. 여기서 subject로 unique한 값을 사용하는데, 사용자 이메일을 주로 사용합니다.

  • iss: 토큰 발급자(issuer)
  • sub: 토큰 제목(subject)
  • aud: 토큰 대상자(audience)
  • exp: 토큰 만료 시간(expiration), NumericDate 형식으로 되어있어야함
  • nbf: 토큰 활성 날짜(not before), 이 날이 지나기 전의 토큰은 활성화되지 않습니다
  • iat: 토큰 발급 시간(issued at), 토큰 발급 이후의 경과 시간을 알 수 있습니다.
  • jti: JWT 토큰 식별자(JWT ID), 중복 방지를 위해 사용하며, 일회용 토큰 등에 사용합니다.
B. Public Claim

공개 클레임은 사용자 정의 클레임으로, 공개용 정보를 위해 사용됩니다.충돌 방지를 위해 URI 포맷을 이용하며, 예시는 아래와 같습니다

{
  "https://tistory.com": true
}
C. Private Claim

비공개 클레임은 사용자 정의 클레임으로, 서버와 클라이언트 사이에 임의로 지정한 정보를 저장합니다.

{
	"token_type": access
}

Signature

토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드입니다. 서명은 위에서 만든 헤더와 페이로드의 값을 각각 BASE 64로 인코딩하고, 인코딩된 값을 비밀 키를 이용해 헤더에서 정의한 알고리즘으로 해싱하고, 이값을 다시 BASE64로 인코딩하여 생성합니다.
base64로 인코딩된 첫 번째, 그리고 두 번째 부분이 완성되었다면, 원하는 비밀 키(암호화에 추가할 salt)를 사용하여 암호화합니다. base64 인코딩 한 값은 누구나 쉽게 디코딩할 수 있지만, 서버에서 사용하고 있는 비밀키를 보유한 게 아니라면 해독해 내는데 엄청난 시간과 노력이 들어갈 겁니다.

HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
  • 예시
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

(2) JWT 과정

  1. 유저가 아이디와 비밀번호로 로그인합니다.
  2. 서버 측에서 해당 계정정보를 검증합니다.
  3. 계정 정보가 정확하다면, 서버측에서 유저에게 signed 토큰을 발급해줍니다.
  4. 클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청할 때마다, 해당 토큰을 함께 서버에 전달합니다.
  5. 서버는 토큰을 검증하고, 요청에 응답합니다.

(3) JWT 한계

  • 쿠키, 세션 때와는 다르게 base64 인코딩을 통한 정보를 전달하므로 전달량이 많습니다. 따라서 네트워크 전달 시 많은 데이터양으로 부하가 생길 수 있습니다.
  • Payload에는 암호화가 되어있지 않기 때문에 민감정보를 저장할 수 없습니다.
  • 토큰이 탈취당하면 만료될 때까지 대처가 불가능합니다.

Refresh Token

토큰이 탈취될 때를 대비하여, JWT를 처음 발급 할 때 Access Token 과 Refresh Token을 함께 발급하고 토큰의 만료시간을 짧게 합니다.
Refresh Token은 Access Token을 Refresh 해주는 것을 보장하는 토큰입니다.
Refresh Token은 다음과 같이 동작합니다.

  1. 클라이언트가 ID, PW로 서버에게 인증을 요청하고 서버는 이를 확인하여 Access Token과 Refresh Token을 발급합니다.
  2. 클라이언트는 이를 받아 Refresh Token을 본인이 잘 저장하고 Access Token을 가지고 서버에 자유롭게 요청합니다.
  3. 요청하는 도중 Access Token이 만료되어 더 이상 사용할 수 없다는 오류를 서버로부터 전달받습니다.
  4. 클라이언트는 본인이 사용한 Access Token이 만료되었다는 사실을 인지하고 본인이 가지고 있던 Refresh Token을 받아 서버의 Access Token의 발급을 요청합니다.
  5. 서버는 Refresh Token을 받아 서버의 Refresh Token Storage에 해당 토큰이 있는지 확인하고, 있다면 Access Token을 생성하여 전달합니다.
  6. 이후 2로 돌아가서 동일한 작업을 진행합니다.

4. Access Token과 Refresh Token

Access Token을 통한 인증 방식의 문제점은 제 3자에게 탈취당할 경우


5. JWT 사용

(1) JWT 모듈 설치

npm i jsonwebtoken

모듈을 먼저 설치합니다.

(2) 환경변수 설정

jwt 인증에 사용할 비밀키를 환경변수에 등록해줍니다. 비밀키는 소스코드에 입력해도 되지만, 외부에 노출되지않도록 환경변수에 등록해 관리하는게 좋습니다,
.env파일에 JWT 인증에 사용할 비밀키를 입력해줍니다.

ACCESS_SECRET=비밀키 설정
REFRESH_SECRET=비밀키 설정

(3) 토큰 생성

토큰 생성을 위해서는 sign이라는 메서드를 사용합니다.

let jwt = require("jsonwebtoken"); //모듈을 호출합니다,
require("dotenv").congfig() 

router.get("/login", function(req,res,next){
  // default : HMAC SHA256
  let token = jwt.sign({
        email: "foo@example.com"   // 토큰의 내용(payload)
      },
      process.env.ACCESS_SECRET,    // 비밀 키
      {
        expiresIn: '5m'    // 유효 시간은 5분
      })


  models.user.find({ //데이터 베이스
    where: {
      email: "foo@example.com"
    }
  })
  .then( user => {
    if(user.pwd === "1234"){
      res.cookie("user", token);
      res.json({
        token: token
      })
    }
  })
})

module.exports = router;
  1. jwt 객체가 sing()메서드를 호출해서 토큰을 생성합니다.
  2. sequelize를 사용해서 요청한 이메일 주소에 해당하는 정보를 DB에서 조회합니다.
  3. 해당 객체의 비밀번호가 맞으면 쿠키에 user라는 이름으로 token값을 저장합니다.
  4. 쿠키는 위변조의 위험이 있지만, jwt는 암호화와 해싱을 거치기 때문에 무결성을 보장합니다.

jwt.sign()

  • sign() 메서드는 기본값으로 HMAC SHA256 알고리즘을 사용합니다.
  • 첫 번째 인자에는 payload, 내용을 작성합니다.
  • 두 번째 인자에는 비밀키를 전달합니다. 환경변수에 등록해놓았다면 process.env.ACCESS_SECRET이 될 것입니다.
  • 세 번쨰 인자는 토큰에 대한 정보를 객체로 전달합니다.
  • 네 번째 인자는 콜백함수를 작성합니다.\

(4) 토큰 유효성

토큰 유효성확인은 verify를 사용합니다.

  • 첫 번째 인자로는 token값을 전달합니다.
  • 두 번째 인자에는 secret key값을 작성합니다.

출처

https://oopsys.tistory.com/277
https://behonestar.tistory.com/37
https://velopert.com/2350
https://mangkyu.tistory.com/56
https://velopert.com/2389
https://brunch.co.kr/@jinyoungchoi95/1
https://victorydntmd.tistory.com/116

profile
읽고 기록하고 고민하고 사용하고 개발하자!

0개의 댓글