로그인

MINKY·2023년 9월 5일

CS스터디

목록 보기
2/7

로그인

HTTP의 특징

  • HTTP의 특징 중 하나는 상태없음(stateless)
  • HTTP 요청을 통해 데이터를 주고 받을 때 요청이 끝나면 요청한 사용자의 정보 등을 유지하지 않는 특징
  • 따라서 요청을 할 때 마다 서버에 연결을 해야한다.

Session기반 인증 방식

Session

방문자가 웹서버에 접속해 있는 상태를 하나의 단위로 보고 그것을 session 이라고 함
웹서버는 이러한 각 단위에 세션 ID를 부여하고 같은 브라우저인지 구별한다.
브라우저를 닫거나 서버에서 이 세션 ID가 들어있는 쿠키를 삭제했을 때 삭제가 된다.
session을 사용한다고 해서 cookie를 안쓰는게 아니라 cookie에 중요 정보를 넣지 않았기 때문에 탈취 되더라도 해석이 의미없는 문자열인 세션ID가 들어있다.

  • 세션 : 서버와 클라이언트의 연결이 활성화된 상태
  • 세션ID : 웹 서버 또는 DB에 저장되는 클라이언트의 유니크한 ID

  1. 클라이언트가 서버에게 유저네임과 패스워드를 기반으로 Login 요청을 한다.
  2. 서버에서 login인증 을 하고 정보가 올바르면 세션 객체를 생성하고 세션 ID를 Set-cookie를 통해 클라이언트에게 전달한다.
  3. 세션 객체는 서버에 저장해놓는다.
  4. 클라이언트가 서버에 작업을 요청할 때 요청 헤더에 세션 ID 가 같이 전달된다 (쿠키이므로 통신시 계속 왔다갔다함)
  5. 서버에서는 클라이언트로부터 받은 요청헤더에 세션ID를 확인해서 세션 객체를 검색하고 정보가 있으면 요청한 작업에 대해 응답해주고 통신을 종료한다.

장점

  • 쿠키 방식과 동일하지만 쿠키에 아무런 의미가 없는 세션ID가 저장이 되므로 탈취되더라도 해석할 수 없다.

단점

  • 하지만 해커가 중간에 가로채서 훔친 쿠키로 http 요청을 보낼 수 있는 하이재킹 공격을 당할 수 있다.
    • 해결법
      • HTTPS를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게 한다.
      • 세션에 유효시간을 넣어 유효시간이 끝나면 더 이상 해커가 이용할 수 없게 한다.
  • 사용자의 상태에 관한 데이터를 서버에 저장했을 때 로그인 중인 유저의 수가 늘어난다면 서버의 메모리 과부하가 일어날 수 있음
  • DB 중 RDBMS에 저장한다면 직렬화 및 역직렬화에 관한 오버헤드가 발생함

RDBMS

  • 관계형 데이터 베이스

직렬화와 역직렬화

  • 오버헤드 어떤 작업을 수행할 때 추가적인 비용이나 부담
  • 바이트 스트림 데이터를 연속적인 바이트들로 처리하는 방식, 데이터의 형식과 내용을 구분하지 않고 순차적으로 다룬다.
  • 직렬화 오버헤드 데이터나 객체를 직렬화하려면 해당 데이터를 바이트 스트림으로 변환해야 하는데 이 작업은 CPU 연산 및 메모리 할당을 필요로 한다.
  • 역직렬화 오버헤드 직렬화된 데이터를 역직렬화 하려면 바이트 스트림을 다시 원래의 데이터 구조로 변환해야 한다. 이 작업 역시 CPU 연산과 메모리 할당이 필요
  • 직렬화(Serialization)와 역직렬화(Deserialization)

  • 실습
    // 사용자가 보낸 값을 보안을 위해 escape하기 위한 모듈입니다. // 예를 들어 foo&bar>>foo&bar 로 바꿉니다.
    const escapeHtml = require('escape-html')
    const express = require('express')
    const session = require('express-session')
    const app = express()
    // 박종선바보멍청이 >> SHA256 >>
    // 9e8821c8ef4ab43ba09310af54e98caedc13e314efdea720bf513b9b3675faf4 app.use(session({
    name: "session-id",
    secret:
    "9e8821c8ef4ab43ba09310af54e98caedc13e314efdea720bf513b9b3675faf4"
    ,
      resave: false,
      saveUninitialized: false
    }))
    // 미들웨어 : auth
    const isAuthenticated = (req,res, next) =>{
    // 만약 세션이 있다면 다음 미들웨어로. 그게 아니라면. 다음 route로 제어권을 넘깁니다.
    if (req.session.user) next()
    else next('route') }
    // 만약 isAuthenticated 하다면 logout을 보여준다. app.get('/', isAuthenticated, function (req, res) {
    res.send(escapeHtml(req.session.user) + '님 환영합니다!') })
    // 만약 isAuthenticated 하지 않다면 login을 보여준다. app.get('/', function (req, res) {
    res.send('<p>로그인</p><form action="/login" method="post">' + 'Username: <input name="user"><br>' +
    'Password: <input name="pass" type="password"><br>' + '<input type="submit" text="Login"></form>')
    • 미들웨어

      여러가지 페이지들이 있을 것이다. 로그인을 무조건 해야 내가 만든 서비스를 이용하게 만든다. ⇒ 페이지마다 확인하는걸 만들 수 있지만 불편하다. 우리 서비스에 들어오는 모든 요청에 대해 어떤 미들웨어를 두는 것 이걸 기반으로 로그인 했는지 안했는지 알 수 있다.

토큰 기반 인증 방식

  • state를 모두 토큰 자체만으로 처리하며 토큰을 처리하는 한 서버를 두고 다른 컨텐츠를 제공하는 서버는 모두 stateless 하게 만들자는 이론이 담긴 방식
  • 세션 방식과 큰 차이는 인증 정보를 서버쪽에 저장하지 않는다는 것
  • 만약 장바구니 로직에 토큰에 관한 인증도 얹었을 경우 장바구니가 오류나면 인증 자체가 안됨 → 인증 관련한 로직은 따로 서버를 둬서 처리해야 한다.
  • 토큰은 주로 JWT토큰 이 활용된다.

JWT

  • JSON Web Token 을 의미하며 헤더, 페이로드, 서명으로 이루어져 있다. JSON 객체로 인코딩되며 메시지 인증, 암호화에 사용됨


  1. 클라이언트가 서버에게 로그인 정보가 들어있는 파라미터와 함께 login 요청을 한다. (세션과 동일)
  2. 로그인 정보가 올바르면 서버에서 json형식으로 된 토큰을 발행한다.
    1. 이 토큰이 클라이언트에게 보내지기 때문에 탈취당할 위험이 있으므로 비밀번호와 같은 중요 정보는 넣지 않아야 함
  3. 발급한 후 JWT를 브라우저로 보낸다 (헤더 또는 쿠키 등을 통해)
  4. 이후 클라이언트는 이 토큰을 어딘가 (세션스토리지, 로컬 스토리지 등) 저장해 놓았다가 (또는 쿠키) 요청시마다 함께 서버로 보낸다.
  5. 서버에서는 요청과 함께 온 토큰을 해독해서 만료시간이 지났는지, 사용자가 맞는지 등을 확인한다.
  6. 요청 작업에 대해 응답해주고 통신을 종료한다.

장점

  • auth0을 이용하면 아이디를 카카오, 네이버처럼 다른 사이트에서 이용할 수 있다.
  • 서버측 부하를 낮출 수 있고 독립적이기 때문에 능률적으로 접근 권한관리를 할 수 있고 분산/클라우드 기반 인프라 스트럭처에 잘 대응할 수 있다.
  • 별도의 인증 저장소가 필요하지 않다. 인증서버와 db에 의존하지 않아도 된다.
  • 디코딩했을 때 JSON이 나오기 때문에 JSON을 기반으로 쉽게 직렬화, 역직렬화가 가능하다.

단점

  • 서버로부터 받은 토큰이 쿠키 또는 로컬 스토리지, 세션 스토리지에 저장이 되므로 탈취당할 위험은 있다. 따라서 token에 중요 정보를 넣어 놓지 않아야 한다.
  • 토큰에 넣는 데이터가 많아질 수록 토큰이 길어지는데 매 api호출 시마다 토큰 데이터를 서버에 전달하는데 그만큼 네트워크 대역폭 낭비가 심할 수 있다.
  • 한번 발급된 token은 수정, 폐기가 불가하다.
    • 해결법 : Access token의 Expire time을 짧게 하고 Refresh token을 이용해서 중간중간 Access token을 재발급하게 해서 Access token이 탈취되더라도 해커가 이용할 수 있는 시간을 최소화한다

그럼 비밀번호는 어디에 저장될까?

  • 일반적으로 평문으로 저장되는 것은 보안상 매우 위험하다. 대신에 안전한 방식으로 저장하기 위해 해시함수솔트(salt) 사용한다.
  • 해시 함수 비밀번호와 같은 입력 데이터를 고정된 길이의 임의의 값으로 변환하는 함수
    • 일반적으로 일방향 함수로 같은 입력에 대해서는 같은 값을 반환하지만 해시값에서 원래 입력값을 복원할 수 없는 특성을 가지고 있다.
    • 해시값으로 비밀번호를 대체하면 실제 비밀번호를 저장하지 않고도 로그인 시에 입력된 비밀번호를 해시화하여 저장된 해시값과 비교하여 인증 가능
    • 해시에 의해 암호화된 데이터를 다이제스트 라고 한다
    • 레인보우 테이블, 무차별 대입 공격에 당할 수 있다.
    • https://crackstation.net/ 여기 넣으면 해시 함수 원본 나온다.

  • 해시 함수 여러 번 수행하기 키 스트레칭
    • 해시함수를 통해 나온 다이제스트를 다시 해시함수에 넣는다.
    • 여러번 돌릴 수록 최종 다이제스트의 원문 메시지를 얻기 위해 소모되는 시간은 더욱 많이 소요되므로 브루트 포스가 최대한 무력화 된다.
  • 솔트 해시화된 비밀번호를 더 안전하게 만들기 위해 사용되는 임의의 값 소금친다
    • 비밀번호에 솔트를 추가하고 해시 함수를 적용하여 해시값을 생성한다.
    • 이렇게 함으로써 동일한 비밀번호도 서로 다른 솔트를 가지고 다른 해시값을 생성하게 되며, 레인보우 테이블과 같은 미리 계산된 해시 값의 공격을 어렵게 만든다.

  • 절차
    • 사용자가 비밀번호를 등록하거나 변경할 때, 비밀번호와 함께 임의의 솔트를 생성한다
    • 솔트를 비밀번호에 추가하고, 그 값을 해시 함수에 입력하여 해시값 계산
    • 계산된 해시값과 솔트를 함께 저장
  • 로그인 시
    • 사용자가 비밀번호를 입력하면, 저장된 솔트와 함께 해시 함수에 입력하여 해시값을 계산
    • 저장된 해시값과 계산된 해시값을 비교하여 인증여부 확인

access 토큰, refresh 토큰

  • 토큰기반인증방식을 구현할 때는 refresh토큰과 access토큰 을 기반으로 구현한다.
  • access토큰의 수명은 짧게, refresh토큰의 수명은 길게 한다.
  • refresh토큰 은 access토큰이 만료되었을 때 다시 access 토큰을 얻기 위해 사용되는 토큰
    • 이를 통해 access토큰이 만료됐을 때 마다 인증에 관한 비용이 줄어들게 된다.
    • 로그인을 하게 되면 access토큰과 refresh토큰 두개를 얻는다.

주의할 점

  • 이렇게 access 토큰을 얻었다면 그 이후에 요청을 할 때는 HTTP Header - Authorization 또는 HTTP Header - Cookie에 담아 요청을 하게 되는데 이 때 다음과 같은 규칙을 지키는 것이 좋다
    • Bearer 으로 Bearer 을 앞에 둬서 토큰기반인증방식이라는 것을 알려주어야
      합니다.
      - HTTP 헤더에서 토큰 기반 인증을 나타내는 표준 규약이기 때문에
    • https를 사용해야 합니다.
    • 쿠키에 저장한다면 sameSite: 'Strict'을 써야 합니다.
    • 수명이 짧은 access token을 발급해야 합니다.
    • url에 토큰을 전달하지 말아야 합니다.

참고
https://80000coding.oopy.io/1f213f10-185c-4b4e-8372-119402fecdd0

https://st-lab.tistory.com/100

https://brilliantdevelop.tistory.com/33

https://code-lab1.tistory.com/321#google_vignette

https://blog.skby.net/l4-스위치와-l7-스위치-비교/

https://m.post.naver.com/viewer/postView.naver?volumeNo=27046347&memberNo=2521903

0개의 댓글