Section 3 Unit7 - [Backend] 인증 / 보안

BRANDY·2023년 3월 8일
0

쿠키(Cookie)

어떤 웹사이트에 들어갔을 때, 서버가 일방적으로 클라이언트에 전달하는 작은 데이터
서버가 웹 브라우저에 정보를 저장하고 불러올 수 있는 수단으로 인증에 필요한 기본 지식이다. 쿠키를 이용한다는 것은 서버에서 클라이언트에 쿠키를 전송하는 것, 클라이언트에서 서버로 쿠키를 다시 전송하는것도 포함한다.

데이터를 저장한 이후 아무 때나 데이터를 가져오는것은 아니며 특정 조건들이 만족 되어야 한다.

'Set-Cookie':[
            'cookie=yummy', 
            'Secure=Secure; Secure',
            'HttpOnly=HttpOnly; HttpOnly',
            'Path=Path; Path=/cookie',
            'Doamin=Domain; Domain=codestates.com'
        ]

조건들은 http 헤더를 사용해 쿠키 옵션으로 표현할 수 있다.

쿠키 옵션 종류

Domain

도메인은 www.google.com과 같은 서버에 접속할 수 있는 이름이며 쿠키 옵션에서의 도메인은 포트 및 서브 도메인 정보, 세부 경로를 포함하지 않는다.
여기서 서브 도메인이란 www같은 도메인 앞에 추가로 작성되는 부분이다.

요청해야 할 URL : http://www.localhost.com:3000/users/login 이라면
Domain은 : localhost.com 이 된다.

만약 쿠키 옵션에 도메인 정보가 존재한다면 클라이언트에서는 쿠키의 도메인 옵션과 서버의 도메인이 일치해야만 쿠키를 전송할 수 있다. 다른 사이트에서 받은 쿠키를 전송하는 일을 막아 준다.

Path

세부 경로로써 서버가 라우팅할 때 사용하는 경로를 의미한다.
요청해야 할 URL : http://www.localhost.com:3000/users/login 이라면
Path는 : /users/login
이를 명시하지 않으면 기본적으로 / 로 설정되어 있다.
설정된 경로를 포함하는 하위 경로로 요청을 해도 쿠키가 전송된다. Path가 /user로 설정되어 있고 요청하는 하위경로가 /users/naver 라면 naver에도 쿠키 전송이 가능하다.
하지만 /posts/naver 로 전송되는 요청은 Path 옵션(/users)을 만족하지 못하기 때문에 서버로 쿠키를 전송할 수 없다.

MaxAge or Expires

쿠키의 유효기간을 정하는 옵션이다. 쿠키를 계속 남겨 놓는다면 탈취당하여 해킹당할 수 있기 때문에 보안측면에서 중요하다.
MaxAge는 유효시간을 초 단위로 설정
Expire는 유효시간을 지정된 시간, 날짜로 정할 수 있다.

쿠키는 위 옵션에 따라 세션쿠키와 영속성 쿠키로 나눠진다.

세션쿠키 : MaxAge, Expires가 없는 쿠키이며 브라우저가 실행중일때 사용하는 임시 쿠키이고 브라우저를 종료하면 쿠키가 삭제된다.
영속성쿠키 : MaxAge, Expires에 지정된 유효시간만큼 사용가능한 쿠키

Secure

사용하는 프로토콜에 따라 쿠키의 전송 여부를 결정하는 옵션이며 Secure 옵션이 true이면 HTTPS를 사용하는 경우만 전송할 수 있다.
옵션이 없다면 http, https에 모두 쿠키를 전송할 수 있고 도메인이 localhost인 경우에는 개발단계에 주로 전송하기 때문에 전송이 가능하다.(예외)

HttpOnly

자바스크립트로 브라우저의 쿠키에 접근이 가능한지 여부를 결정한다. ture라면 자바스크립트로 쿠키 접근이 불가하다.
옵션을 명시하지 않으면 기본적으로 false이며 이는 document.cookie를 이용해 쿠키에 접근할 수 있고, 탈취될 위험이 있다.

SameSite

Cross-Origin 요청을 받은 경우, 요청에서 사용한 메소드(e.g. GET, POST, PUT, PATCH …)와 해당 옵션의 조합을 기준으로 서버의 쿠키 전송 여부를 결정하게 됩니다. 사용 가능한 옵션은 다음과 같습니다.

Lax: Cross-Origin 요청이라면 GET 메소드에 대해서만 쿠키를 전송할 수 있습니다.
Strict: 단어 그대로 가장 엄격한 옵션으로, Cross-Origin이 아닌 same-site 인 경우에만 쿠키를 전송 할 수 있습니다.
None: Cross-Origin에 대해 가장 관대한 옵션으로 항상 쿠키를 보내줄 수 있습니다. 다만 쿠키 옵션 중 Secure 옵션이 필요합니다.
이때 same-site는 요청을 보낸 Origin과 서버의 도메인, 프로토콜, 포트가 같은 경우를 말합니다. 이 중 하나라도 다르다면 Cross-Origin으로 구분됩니다.

서버에서 이러한 옵션들을 지정하여 클라이언트로 쿠키를 처음 전송하게 된다면 헤더에 Set-Cookie라는 프로퍼티로 쿠키를 담아 전송한다.
이후 클라이언트에서 서버에 쿠키를 전송해야 한다면 클라이언트는 헤더에 Cookie라는 프로퍼티에 쿠키를 담아 전송한다.

쿠키에는 민감한 정보를 담는것을 피하는것이 좋다.

세션기반 인증(Session-based Authentication)

로그인

로그인을 통해 사용자의 인증정보를 저장하고 이 인증된 사용자가 어떤식으로 웹사이트를 이용하게 되는지 알아보자.

사용자가 정확한 아이디와 비밀번호를 입력한다면 서버는 인증에 성공했다고 판단하고 유저의 로그인 상태를 유지한다. 인증에 따라 리소스의 접근 권한이 달라지며 서버는 사용자가 인증에 성공했음을 알고 있어야 하고 클라이언트는 인증 성공을 증명할 수단을 가지고 있어야 한다.

사용자가 인증에 성공한 상태는 세션이라고 부르며 일종의 저장소에 세션을 저장한다. 주로 in-memory, 세션스토어에 저장하고 세션을 구분하는 세션 아이디를 만드는데 이것을 세션 성공을 증명할 수단으로 사용한다.

이때 로그인을 유지하기 위한 수단으로는 쿠키를 사용한다. 쿠키에는 서버에서 발급한 세션아이디를 저장한다.
쿠키를 통해 유효한 세션아이디가 서버에 전달되고, 세션 스토어에 해당 세션이 존재한다면 서버는 해당 요청에 접근 가능하다고 판단한다.
하지만 쿠키에 세션아이디 정보가 없으면 인증이 안되었음을 알려준다.

로그아웃

로그아웃을 구현하기 위해서는 서버는 세션 정보를 삭제해야 하며 클라이언트는 쿠키를 갱신하거나 삭제해야 한다. 클라이언트에서 세션 정보를 없애기 위해서는 res.cookie로 쿠키의 값을 무효한 값으로 갱신하거나 res.clearCookie로 쿠키를 삭제해버리면 된다.

express-session

Node.js에서 이런 세션을 대신 관리해주는 express-session 이라는 모듈이 존재한다.

const express = require('express');
const session = require('express-session');

const app = express();

app.use(
  session({
    secret: '@codestates',
    resave: false,
    saveUninitialized: true,
    cookie: {
      domain: 'localhost',
      path: '/',
      maxAge: 24 * 6 * 60 * 10000,
      sameSite: 'none',
      httpOnly: false,
      secure: true,
    },
  })
);

위와 같이 세션의 옵션을 지정할 수 있다.
secret 옵션의 비밀키를 이용해 암호화된 세션 id를 생성한다. 이를 쿠키를 통해 클라이언트에 전송하며 쿠키로 전송된 세션 id는 이에 종속되는 고유한 세션 객체를 가지며 이는 서버에 저장된다.
이때 세션 객체는 유저별로 독립적으로 유저별 각각 다른 데이터를 저장할 수 있다. 따라서 클라이언트에 유저의 개인정보를 담지 않고도 유저의 인증여부를 판단할 수 있다. 세션 객체는 req.session으로 접근할 수 있으며 세션에 임의의 데이터를 저장하거나 불러올 수 있다.

해싱

가장 많이 쓰이는 암호화 방식이며 복호화가 불가능하고 암호화만 가능하다.
해시 함수(Hash Function)을 사용하며 항상 같은 길이의 문자열을 리턴하고 서로 다른 문자열에 동일한 해시 함수를 사용하면 반드시 다른 결과값이 나온다. 또한 동일한 문자열에 동일한 해시 함수를 사용하면 항상 같은 결과값이 나온다.

항상 같은 결과값이 나오는 특성을 이용해 이전 값을 알아 낼 수 있는 표인 레인보우 테이블이 존재한다. 이는 해싱을 했더라도 해싱 이전의 값을 알 수 있기 때문에 보안상 위협이 될 수 있다.

솔트

이 보안상 위협을 막기 위해 솔트(Salt)를 사용하는데 데이터가 유출되더라도 해싱 이전의 값을 알아내기 어렵도록 해싱 이전의 값에 임의의 값을 더하는 것이다.

해시 함수의 리턴값이 달라진 것을 알 수 있다.

해싱의 목적은 데이터 자체를 사용하는것이 아니고 동일한 값의 데이터를 사용하고 있는지 여부만 확인하는 것이 목적이다. 우리가 로그인을 할때 그 아이디와 비밀번호를 관리자가 알고 있어야할까? 아니다. 이는 개인정보기 때문에 민감하게 다루어져야 한다. 이때 해싱한 값으로 일치 여부를 확인하여 해싱 값만으로 로그인 요청을 처리할 수 있다.
이처럼 해싱은 민감한 데이터를 다루어야 하는 상황에서 데이터 유출의 위험성은 줄이면서 데이터의 유효성을 검증하기 위해서 사용되는 단방향 암호화 방식이다.

토큰

최근 웹 애플리케이션에서 가장 많이 사용되는 인증방식중 하나로 사용자의 인증 정보를 서버가 아닌 클라이언트에 저장한다.
기존의 세션 기반 인증은 서버에 인증정보를 저장하고 상태를 관리하는데 이는 서버에 부담이 되므로 클라이언트에 이를 저장하는 방법으로 고안된것이 토큰방식이다. 서버의 부하, 메모리 부족 문제를 줄일 수 있다.

인증과 권한 정보를 담고 있는 암호화된 문자열이며 인증 방식의 흐름은 다음과 같다

  1. 사용자가 인증 정보를 담아 서버에 로그인 요청을 보냅니다.

  2. 서버는 데이터베이스에 저장된 사용자의 인증 정보를 확인합니다.

  3. 인증에 성공했다면 해당 사용자의 인증 및 권한 정보를 서버의 비밀 키와 함께 토큰으로 암호화합니다.

  4. 생성된 토큰을 클라이언트로 전달합니다.
    HTTP 상에서 인증 토큰을 보내기 위해 사용하는 헤더인 Authorization 헤더를 사용하거나, 쿠키로 전달하는 등의 방법을 사용합니다.

  5. 클라이언트는 전달받은 토큰을 저장합니다.
    저장하는 위치는 Local Storage, Session Storage, Cookie 등 다양합니다.

  6. 클라이언트가 서버로 리소스를 요청할 때 토큰을 함께 전달합니다.
    토큰을 보낼 때에도 Authorization 헤더를 사용하거나 쿠키로 전달할 수 있습니다.

  7. 서버는 전달받은 토큰을 서버의 비밀 키를 통해 검증합니다. 이를 통해 토큰이 위조되었는지 혹은 토큰의 유효 기간이 지나지 않았는지 등을 확인할 수 있습니다.

  8. 토큰이 유효하다면 클라이언트의 요청에 대한 응답 데이터를 전송합니다.

토큰 인증 방식의 장점

무상태성 : 서버가 유저의 인증 상태를 관리하지 않습니다. 서버는 비밀 키를 통해 클라이언트에서 보낸 토큰의 유효성만 검증하면 되기 때문에 무상태적인 아키텍처를 구축할 수 있습니다.

확장성 : 다수의 서버가 공통된 세션 데이터를 가질 필요가 없다는 것도 토큰 기반 인증의 장점입니다. 이를 통해 서버를 확장하기 더 용이합니다.

어디서나 토큰 생성 가능 : 토큰의 생성과 검증이 하나의 서버에서 이루어지지 않아도 되기 때문에 토큰 생성만을 담당하는 서버를 구축할 수 있습니다. 이를 잘 활용하면 여러 서비스 간의 공통된 인증 서버를 구현할 수 있습니다.

권한 부여에 용이 : 토큰은 인증 상태, 접근 권한 등 다양한 정보를 담을 수 있기 때문에 사용자 권한 부여에 용이합니다. 이를 활용해 어드민 권한 부여 및 정보에 접근할 수 있는 범위도 설정할 수 있습니다.

JWT(JSON Web Token)

토큰 기반 인증 구현시 대표적으로 사용하는 기술이며 JSON객체에 정보를 담고 이를 토큰으로 암호화 하여 전송할 수 있다. 클라이언트가 서버에 요청을 보낼 때, 인증정보를 암호화된 JWT토큰으로 제공하고 서버는 이 토큰을 검증하여 인증정보를 확인할 수 있다.

  1. Header
    해당 토큰 자체를 설명하는 데이터가 담겨있다. 토큰의 종류, 시그니처를 만들때 사용할 알고리즘을 JSON형태로 작성한다
{
  "alg": "HS256",
  "typ": "JWT"
}

위와같은 JSON객체를 base64방식으로 인코딩하면 Header가 완성되는데, base64방식은 디코딩이 가능한 인코딩 방식이므로 민감한 정보를 담지 않는다.

  1. Payload
    내용물을 담고 있는 부분으로 어떤 정보에 접근 가능한지에 대한 권한, 유저의 이름과 같은 개인정보, 토큰의 발급 시간 및 맟료시간 등의 정보들을 JSON형태로 담는다.
{
  "sub": "someInformation",
  "name": "phillip",
  "iat": 151623391
}

위와같은 JSON객체를 base64로 인코딩하면 JWT의 Payload가 완성된다.

  1. Signature
    토큰의 무결성을 확인할 수 있는 부분으로 서버의 비밀 키(암호화에 추가할 salt)와 Header에서 지정한 알고리즘을 사용해서 해싱한다.
    예를들어 HMAC SHA256 알고리즘을 사용한다면 Signature는 아래와 같은 방식으로 생성

HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);

토큰을 발급할 때 사용한 Secret를 정확하게 알고 있지 못한다면 유효한 Signature를 만들어낼 수 없기 때문에 서버는 Signiture를 검증하는 단계에서 올바르지 않은 토큰임을 알아 낼 수 있다.

토큰 인증 방식의 한계

Signature를 통해 위조된 토큰은 알아낼 수 있지만 토큰 자체를 탈취당한다면 토큰 인증 방식의 한계가 드러난다.

무상태성

인증 상태를 관리하는 주체가 서버가 아니어서 토큰이 탈취된다 해도 토큰을 강제로 만료시킬 수 없다. 따라서 토큰이 만료될 때까지 사용자로 가장하여 요청을 계속 보낼 수 있다.

유효기간

토큰이 탈취될 것을 생각하여 유효 기간을 짧게 설정하면 사용자는 만료될때마다 로그인을 다시 해야하여 사용자 경험이 안좋아지고, 유효 기간이 길어진다면 토큰 탈취에 치명적으로 작용할 수 있다.

토큰의 크기

토큰에 여러 정보를 담을 수 있는 만큼 많은 데이터를 담으면 암호화 하는 과정도 길어지고 토큰의 크기도 커지기 때문에 네트워크 비용 문제가 발생할 수 있다

액세스 토큰(Access Toekn)과 리프레시 토큰(Refresh Token)

이러한 토큰 인증의 한계를 극복하기 위한 방법 중 하나로 액세스토큰과 리프레시 토큰을 함게 사용한다

Access Token
서버에 접근하기 위한 토큰으로 보안을 위해 24시간 정도의 짧은 유효기간이 설정되어 있다.

Refresh Token
서버 접근을 위한 토큰이 아닌 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰으로 엑세스 토큰보다 긴 유효기간을 설정한다.

이렇게 두 가지의 각기 다른 토큰을 사용하면 액세스 토큰이 만료되더라도 리프레시 토큰의 유효기간이 남아있다면 사용자는 다시 로그인을 할 필요 없이 지속해서 인증상태를 유지할 수 있다.

OAuth

소셜 로그인 인증방식은 OAuth 2.0이라는 기술을 바탕으로 구현한다. 이는 인증을 중개해주는 메커니즘으로 보안된 리소스에 액세스 하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 프로토콜이다.

웹 서비스에서 사용자의 인증을 대신 해주고, 접근 권한에 대한 토큰을 발급한 후 이를 이용해 서버에서 인증이 가능해진다.

유저의 민감한 정보가 직접 App에 노출될 일이 없고 인증 권한에 대한 허가를 미리 유저에게 구해야 하기 때문에 더 안전하게 사용할 수 있다.

OAuth 작동 메커니즘

Resource Owner

OAuth 인증을 통해 소셜 로그인을 하고싶어하는 사용자를 Resource Owner라고 하며
Resource는 사용자의 이름, 전화번호 등의 정보를 뜻한다. 이러한 정보의 주인이 바로 사용자이기 때문에 Resource Owner라고 한다.

Resource Server & Authorization Server

사용자가 소셜 로그인을 하기 위해서 사용하는, 이미 사용중인 서비스(Naver, Kakao, Google 등)의 서버 중 사용자의 정보를 저장하고 있는 서버를 특정해서 Resource Server라고 부른다.
이미 사용중인 서비스의 서버 중 인증을 담당하는 서버는 Authorization Server라고 부른다.

Application

사용자가 소셜 로그인을 활용해 이용하고자하는 새로운 서비스는 환경에 따라서 조금씩 다르게 불리며 여기서는 Application이라고 지칭하자.
경우에 따라서 Applicaiton을 Client와 Server로 세분화해서 지칭하기도 한다.

OAuth 인증 방식의 종류와 흐름

  1. Implicit Grant Type

    보안성이 떨어지기 때문에 잘 사용하지 않는 방식.

  2. Authorization Code Grant Type

    Authorization Code를 사용한 인증 단계가 추가로 있기 때문에 비교적 안전하지만

사용자가 새로운 서비스를 이용하다가 액세스 토큰이 만료되면 매번 이 과정을 거쳐서 다시 발급 받아야 하기 때문에 편의성이 좋지 않다.

3.Refresh Token Grant Type

이 방식이 가장 많이 사용될 것 같다. 보안상 문제와 재발급 문제를 해결할 수 있기 때문이다.

OAuth의 장점

  1. 특정 사이트마다 아이디, 비밀번호, 이름, 전화번호 등의 정보를 입력하지 않아도 쉽고 안전하게 새로운 서비스 이용 가능하며 해당 서비스에 직접 노출하는것이 아니므로 직접 가입하는것 보다 안전하다. 또한 애플리케이션의 입장에서도 회원정보를 직접 가지고 있지 않기 때문에 회원 정보 유출의 위험성부분에서도 부담을 덜 수 있다.

  2. OAuth를 통해 로그인 할 때, 사용자가 원하는 정보에만 접근을 허락할 수 있어서 선택적으로 정보를 제공할 수 있다.

profile
프런트엔드 개발자

0개의 댓글