[JavaScript] TWIL : HTTPS와 Authentication (20/12.09~12.13)

정빈·2020년 12월 20일
0
post-custom-banner

HTTPSAuthentication(인증)에 대해서 학습했다.

HTTPS

HTTPS(Hyper Text Transfer Protocol Secure Socket layer)는 웹 통신 프로토콜인 HTTP에 Secure를 더한, 보안이 강화된 버전이다.

HTTPS는 SSL 혹은 TLS라는 알고리즘을 이용해, HTTP 통신을 하는 과정에서 내용을 암호화하여 데이터를 전송한다.

여기서, SSL과 TLS는 무엇일까?

  • SSL(Secure Socket Layer) 프로토콜은 CA(Certificate Authority)라 불리는 서드 파티로부터 서버와 클라이언트의 인증을 하는데 사용된다고 한다.
  • TLS(Transport Layer Security Protocol) 또한 SSL과 같은 의미다. Netscape사에서 SSL이 발명되었고, 표준화 기구인 IETF의 관리로 넘어가면서 TLS라는 이름으로 바뀌었다고 한다.

어쨌든, SSL의 핵심은 암호화이다. 보안과 성능상의 이유로 '대칭키'와 '공개키'라는 두 가지 암호화 기법을 혼용해서 사용한다고 한다.

자, 대칭키와 공개키에 대한 개념을 짚고 넘어가야 할 듯 하다.

  • 대칭키란, 동일한 키(비밀번호)로 암호화와 복호화(암호화 해독)를 같이 할 수 있는 방식의 암호화 기법이다. 키가 노출되면 암호화가 무용지물이 되는 단점이 있다.
  • 공개키란, A와 B 두 개의 키를 이용하는데, A키를 암호화하면 B키로 복호화 할 수 있고, B키로 암호화하면 A키로 복호화할 수 있는 방식이다. 둘 중 하나를 비공개키로, 하나를 공개키로 지정해 비공개키는 자신만 가지고있고, 공개키를 타인에게 제공한다. 공개키가 유출되거나 갈취당해도 비공개키가 있어야만 복호화가 가능하기 때문에 보안이 유지된다.

인증에서 HTTPS를 사용해야하는 이유는, HTTP보다 상대적으로 안전하며 데이터 제공자의 신원을 보장받을 수 있기 때문이다. 브라우저가 응답과 함께 전달된 인증서 정보를 확인가능하기 때문에, CA로부터 발급받은 인증서를 이용해 응답받은 데이터의 안전성을 판단할 수 있다.

+) CA(Certificate Authority) : 클라이언트가 접속한 서버가 클라이언트가 의도한 서버가 맞는지를 보장하는 인증서를 발급하는 기업들을 칭한다. 신뢰성이 엄격하게 공인된 기업들만 참여할 수 있다.



Authentication

Cookie(이하 쿠키)란, HTTP(하이퍼 텍스트 기록서)의 일종으로 서버가 일방적으로 클라이언트에 전달하는 이름과 값 쌍과 속성을 가진 작은 데이터이다.
HTTP는 stateless(무상태)라는 특징을 가지고 있다. 요청과 응답이라는 1회성 통신을 하기 때문에 상태를 가지지 않는다. 이를 해결하기 위해 쿠키를 사용할 수 있다.
클라이언트가 서버로부터 쿠키를 받았다면, 클라이언트는 이후 자동으로 http 요청 시 쿠키를 함께 서버에 전달하게 된다. 클라이언트가 '나야 나'라는 정보를 서버에게 요청 때마다 전달하기 때문에 서버가 '어 그래 너구나'하고 알아볼 수 있는 것이다.
그러나, 이런 쿠키에는 유저의 인증정보가 그대로 담기기 때문에 쿠키가 중간에 갈취당한다면 정보는 그대로 타인에게 노출된다. 그러니 사용자의 인증을 유지하는 방법에는 쿠키를 써서는 안되겠다.

쿠키를 설정할 수 있는 많은 옵션들이 있다.

  • domain : String, 서버와 요청의 도메인이 일치하는 경우 쿠키를 전송
  • path : String, 서버와 요청의 세부경로가 일치하는 경우 쿠키를 전송
  • maxAge or expires : Number(millisecond) or String(DateTime), 쿠키의 유효기간 설정
  • httpOnly : Boolean, 스크립트의 쿠키 접근 가능 여부 설정
  • secure : Boolean, HTTPS 프로토콜에서만 쿠키 전송 여부 설정
  • sameSite : String, CORS 요청의 경우 옵션 및 메서드에 따라 쿠키 전송 여부 결정,
    동일한 사이트 요청에만 전송하는 'strict', 동일한 사이트 요청 또는 일부 예외(HTTP GET method)를 허용하는 'lax'(default), 동일한 사이트와 크로스 사이트에 모두 전송하는 'none', 세 가지 설정이 있다.

서버에서 쿠키를 전달 할 때 이렇게 작성할 수 있다.

res.cookie('id', userInfo.id, {   // 순서대로 이름, 값, 옵션 지정
  domain: 'localhost',
  path: '/',
  sameSite: 'none',
  httpOnly: true,
  secure: true,
});

Session

Session(이하 세션)이란, '일정시간' 동안 같은 사용자(브라우저)로부터 들어오는 일련의 요구를 하나의 '상태'로 보고, 그 상태를 일정하게 유지시키는 기술이다.
여기서 일정시간이란 방문자가 브라우저를 통해 웹 서버에 접속한 시점으로부터 브라우저를 종료함으로써 연결을 끝내는 시점을 의미한다.

서버는 쿠키를 이용해 client(브라우저)에 유일하고 암호화된 세션 ID를 부여하며, 사용자의 인증정보는 서버에서 관리한다.
데이터는 서버에서 관리하고, client에게는 그 인증정보에 대한 키만 부여를 하니 직접적인 데이터가 쿠키에 담기지 않아, 누군가 중간에 쿠키를 엿보더라도 인증정보 유출에 대한 걱정이 없다.
그러나, 세션에는 브라우저 탭을 종료하지 않는 이상 유효기간이 따로 정해져 있지 않기 때문에, 누군가 유출된 쿠키를 이용해 서버에 데이터를 요청 할 수 있기 때문에 반드시 로그아웃을 이용해 세션을 파괴하는 작업이 필요하다.

서버는 node.js의 express의 미들웨어인 express-session이라는 모듈을 사용해 세션 객체에 client의 인증정보를 저장, 관리할 수 있다. 웹의 규모가 커짐에 따라, 서버의 부담이 또한 커지는 단점이 존재한다.

// 세션객체 : req.session

// 세션 객체에 유저정보 저장
req.session.userId = userInfo.userId; // userInfo는 DB에서 찾아온 데이터
// req.session.save(callback) 메서드를 이용할 수도 있다. save 메서드는 세션의 store를 이용하는 방법인데, 세션 store는 더 많은 기능이 있다고 한다. 이 부분은 좀 더 찾아봐야 할 듯하다!

// 해당 세션 파괴
req.session.destroy();

Token

Token(이하 토큰)은 우리가 알고있는 '토큰'과 같은 기능이라고 생각 할 수 있다. 세션을 이용함에 따른 서버의 부담을 피하기 위해, client에서 인증 정보를 보관하는 방법을 고안한 것이다.

client가 토큰을 발급받으면, 제대로된 유저 인증을 받은 것으로 취급하고 데이터에 접근을 가능하게 한다. 인증정보를 client에 보관한다는 것에서 보안에 대한 걱정을 할 수 있지만, 정보를 암호화해서 client에 보낸다. 비밀키(salt) 를 더하여 암호화하기 때문에 탈취당한다고 해서 쉽게 복호화 될 수는 없다. 하지만 여전히 위험은 존재하므로, 토큰의 유효기간은 되도록 짧게 주는 것이 좋다.

대표적인 토큰기반 인증은 JWT(JSON Web Token)이 있다.

JWT는 두 가지의 토큰을 발행한다.

  • Access Token : 권한을 부여받는 실질적인 토큰. 유효기간은 되도록 짧게 하자!
  • Refresh Token : 보다 유효기간이 조금 더 긴, Access Token 재발급용 토큰. UX 개선을 위해, Refesh Token가 유효하다면 Access Token이 만료되더라도 사용자가 다시 로그인 하지 않아도 되도록 자동으로 재발급 한다.
    그러나, 역시 Refresh Token 갈취 가능성에 대해 보안상의 이유로 이조차도 발급하지 않는 기업들도 있다. 유저의 편의보다 정보 보안이 더 중요한 기업의 경우이다.

JWT는 '.'으로 나뉘어진 3가지의 구조로 이루어져 있다. (aaaaaa.bbbbbbbb.ccccccccc)

  • Header : 이것이 어떤 종류의 토큰인지, 어떤 알고리즘으로 sing(암호화) 할지에 대해 적혀있다.
  • Payload : 정보가 담겨있다. 어떤 정보에 접근 가능한지에 대한 권한을 담을 수도 있고, 유저이름 등 필요한 데이터를 이곳에 담아 암호화한다. 그래도! 민감한 정보는 되도록 담지 않는 것이 좋겠다.
  • Signature : 원하는 비밀키(salt)를 사용해 header와 payload를 암호화한다.

서버는 환경변수로 salt를 지정해 암호화와 복호화를 할 수 있으며, JWT는 프로미스 객체를 리턴하므로 그에 맞게 다뤄야 한다.

const jwt = require("jsonwebtoken");

// Access, Refresh Token 암호화 발급
jwt.sign(data1, process.env.ACCESS_SECRET, { expiresIn: "1d" })
jwt.sign(data2, process.env.REFRESH_SECRET, { expiresIn: "3d" })

// Access, Refresh Token 복호화
jwt.verify(data, process.env.ACCESS_OR_REFRESH_SECRET)

Oauth 2.0

Oauth 2.0은 우리가 최근 많이 접하는 소셜 로그인 인증방식에 사용되는, 인증을 위한 표준 프로토콜의 한 종류이다. 보안된 정보에 접근하기 위해 client에게 권한을 제공(Authorization)하는 프로세스를 단순화하는 프로토콜 중 한 방법이기도 하다.

회원가입이나 로그인 절차 없이, 이미 사용자 정보를 가지고 있는 웹 서비스(Github, Google, Kakao 등)에서 사용자의 인증을 대신해주고, 접근 권한에 대한 토큰을 발급한 후, 이를 이용해 해당 앱의 서버에서 인증이 가능해진다.

소셜 로그인의 로직 플로우는 이렇게 진행된다.
(3개의 주체가 존재한다. client, Resource Server, Authorization Server)

  1. client가 Authorization Server(Github, Google, ... )에게 Authorization code를 요청한다.
  2. Authorization Server가 redirect url을 통해 client에게 Authorization code를 부여한다.
  3. client가 Resource Server에게 Authorization code를 전달한다.
  4. Resource Server가 Authorization Server에게 전달받은 Authorization code를 보내서 Access Token을 요청한다.
  5. Authorization Server가 Resource Server에게 Access Token을 부여한다.
  6. Resource Server가 client에게 Access Token을 전달한다.

이 과정이 끝나면, client는 부여받은 Access Token을 이용해 Resource Server의 데이터에 접근할 수 있다!

이런 인증방식은 client 딴에서와 server 딴에서의 인증처리가 모두 필요하기 때문에 코드 구현이 훨씬 더 흥미롭다. (ㅎㅎㅎ)



마치며

인증 스프린트는 개인적으로 제일 최고난이도 스프린트였다. 짧은 시간에 많은 인증 개념들 학습과, 구현까지 해내야했기 때문에 제일 숨 쉴틈 없이 3일을 달렸던 것 같다. 하지만 사용자들의 정보 보안에 대한 민감하고 중요한 부분이라는 것을 인지했기 때문에, 할 수 있는 한 집중할 수 있었던 것 같다. 사실상 HA 전 마지막 스프린트였다. 이제 배포 스프린트를 통해 앱 배포 방법을 배우고, 팀프로젝트를 시작하게 된다. 시간 정말 빠르다. 정빈 으아자!!!!

profile
Back-end. You'll Never Walk Alone.
post-custom-banner

0개의 댓글