[Web] 인증(Authentication) / 인가(Authorization)

haejun-kim·2020년 8월 14일
1

[Web]

목록 보기
4/4
post-thumbnail
  • 인증과 인가는 API에서 가장 자주 구현되는 기능 중 하나이다.
  • Private한 API는 물론이고 Public한 API도 기본적인 인증과 인가를 요구한다.

인증(Authentication)

인증은 회원가입과 로그인을 얘기한다.

서비스를 누가? 어떻게 사용하는지 추적이 가능하도록 하기 위해 필요하다.

인증에 필요한 것은 아이디, 이메일 주소, 비밀번호 등이 있다.

  • 알고리즘은 SHA-256을 사용
  • 암호화 방법에는 단방향과 양방향 두가지 방법이 있다.
  • 단방향이 아닌 양방향 암호화는 카드 결제할 때 마다 카드 정보를 매번 입력하지 않는 것, 배송지에 대한 주소를 매번 입력하지 않는 것 등에 사용된다.

로그인 절차

가장 기본적인 로그인 절차는 다음과 같다.

  1. 유저 아이디와 비밀번호 생성
  2. 유저 비밀번호를 암호화 해서 DB에 저장
  3. 유저 로그인 → 아이디와 비밀번호 입력
  4. 유저가 입력한 비밀번호를 암호화 한 후 암호화돼서 DB에 저장 된 유저의 비밀번호와 비교
  5. 일치하면 로그인 성공
  6. 로그인 성공하면 access token을 클라이언트에게 전송
  7. 유저는 로그인 성공 후 다음부터는 access token을 첨부해서 request를 서버에 전송함으로써 매번 로그인 해도 되지 않도록 한다.

유저 비밀번호 암호화

유저의 private 한 정보들을 DB에 그대로 저장하면 개인정보 유출에 막대한 영향을 끼치며, 법적으로도 금지되어 있다. 그렇기 때문에 유저의 비밀번호와 같은 private 한 정보는 개발자도 알아볼 수 없도록 암호화하여 저장하여야 한다. 암호화하여 저장한 비밀번호는 DB가 해킹을 당하더라도 그대로 노출되지 않는다. 이 때 일반적으로 사용하는게 hash 함수이다. hash 함수에 대해서는 아래를 참고하도록 하자.

2. Set, Dictionary, hash

단방향 해시 함수는 원본 메세지를 변환하여 암호화 된 메세지인 digest를 생성한다. 원본 메세지를 알면 암호화 된 메세지를 구분하기는 쉽지만 암호화 된 메세지로는 원본 메세지를 구할 수 없기 때문에 단방향(one-way) 라고 한다.

  • 예를 들어, "test password"를 hash256이라는 해쉬 함수를 사용하면 0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e 값이 나온다.
  • 만일 "test password2"를 hash256 해쉬 함수를 사용하면 d34b32af5c7bc7f54153e2fdddf251550e7011e846b465e64207e8ccda4c1aeb 값이 나온다. 실제 비밀번호는 비슷하지만 해쉬 함수 값은 완전히 틀린것을 볼 수 있다. 이러한 효과를 avalance라고 하는데 비밀번호 해쉬 값을 해킹을 어렵게 만드는 하나의 요소이다.

bcrypt

취약점

위에서 설명한 단방향 해시 함수도 몇가지 취약점이 존재한다.

  • Rainbow table attack : 미리 해시값들을 계산 해 놓은 테이블을 Rainbow table 이라고 한다.
  • 해시 함수는 원래 패스워드를 저장하기 위해서 설계된 것이 아니라 짧은 시간에 데이터를 검색하기 위해 설계된 것이다. 그렇기 때문에 해시 함수는 본래 처리 속도가 최대한 빠르도록 설계되었다. 이러한 속성 때문에 공격자는 매우 빠른 속도로 임의의 문자열의 digest와 해킹할 대상의 digest를 비교할 수 있다. 이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우 비밀번호를 알아내는데 그리 긴 시간을 필요로 하지 않는다.

보완점

  • Salting : 실제 비밀번호 이외에 추가적으로 랜덤 데이터를 더해서 해시값을 계산하는 방법이다.
  • Key Stretching : 키 스트레칭이란 단어에서 유추할 수 있듯 Key를 늘려버리는 것을 뜻한다. 예를 들어, Salting까지 된 후에 이 값을 더 덧붙여서 더 길게 만들어서 검색하는데 걸리는 시간을 훨씬 늘려버리는 것이다.

SaltingKey Stretching을 구현 한 해시 함수 중 가장 널리 사용되는 것이 bcrypt이다. bcrypt는 처음부터 비밀번호를 단방향 암호화 하기 위해 만들어진 해시 함수이다.

JWT (JSON Web Tokens)

위에서 설명 한 로그인 절차를 보면 로그인을 성공 했을 때 access token이라는 것을 발급해준다. access token은 암호화 된 유저 정보를 첨부해서 request를 보내게 된다.

access token이란 일종의 입장 권한을 허가해주는 티켓이라고 생각하면 이해하기 쉽다. 예를 들어, 어떤 페이지에 게시물을 등록하거나 댓글을 달기 위해서는 로그인을 해야한다는 제약 조건이 존재한다고 하자. 이런 경우 로그인을 한 유저에게는 access token이라는 티켓을 발행해주는데, 이 티켓으로 '이 유저는 로그인을 성공적으로 마쳤으니, 게시물과 댓글을 추가할 수 있는 권한을 가지고 있어' 라는 권한을 허가해주는 것을 뜻한다. 그러면 서버에서는 access token을 복화화 해서 해당 유저 정보를 얻게 된다.

  • 이런 절차의 목적은 해당 유저가 매번 로그인 해도 되지 않도록 하는 것이다.
  • access token 을 생성하는 방법은 여러가지가 있는데, 그 중 가장 널리 사용되는 기술 중 하나가 바로 JWT이다.
  • JWT는 말 그대로 유저 정보를 담은 JSON 데이터를 암호화 해서 클라이언트와 서버간에 주고 받는 것이다.

JWT의 구조

헤더(header).내용(payload).서명(signature) ex : ( aaaa.bbbbb.ccccc )

  • 헤더(header) : header 에는 두가지 정보를 지니고 있다.

    typ : JWT와 같은 토큰의 타입을 지정한다.

    alg : 해싱 알고리즘을 지정한다. 이 알고리즘은 토큰을 검증할 때 사용되는 signature 부분에서 사용된다.

  • 내용(payload) : 토큰에 담을 정보가 들어있다. 여기에 담는 정보의 한 조각claim이라고 부르며, 이는 name / value 의 한 쌍으로 이루어져있다. 토큰에는 여러개의 클레임들을 넣을 수 있다.

    사용자 정보에 대한 내용이 필수로 들어가야한다. 내용도 반대로 출력하면 내용을 볼 수 있기 때문에 private 한 정보는 담지 않고 데이터베이스의 user_id와 같은 정보를 넣는다.

    이런 클레임의 종류는 크게 세 분류로 나눠져있다.

    1. 등록된(registered) 클레임

      → 토큰에 대한 정보들을 담기 위하여 이름이 이미 정해진 클레임들이다. 모두 선택적이라는 특징이 있다.

    2. 공개(public) 클레임

      → 충돌이 방지된 이름을 가지고 있어야 한다. 충돌을 방지하기 위해서는 이름을 URI형식으로 짓는다.

    3. 비공개(private) 클레임

      → 클라이언트와 서버 협의 하에 사용되는 클레임 이름들이다. 충돌이 일어날 수 있으니 사용할 때 유의해야한다.

  • 서명(signature) : JWT가 원본 그대로라는 것을 확인할 때 사용하는 부분이다.

    이 서명 부분에는 헤더의 인코딩값과 내용의 인코딩값을 합친 후 주어진 SECRET_KEY로 해시를 하여 생성한다. 서명은 BASE64URL로 인코딩 된 header와 payload 그리고 JWT secret(별도 생성)을 헤더에 지정된 암호 알고리즘으로 암호화 하여 전송한다. ( 복호화 가능 )

    header와 payload는 BASE64 인코딩만 한 것이므로(암호화가 아님) 누구나 원본을 볼 수 있으니 개인정보를 담아서는 안된다.

실제로 위스타그램 로그인 부분에서 bcyptjwt를 활용하여 구현해보았다.

비밀번호 암호화 및 token 발행 ( bcrypt, JWT )

access token, ID, Password를 사용하는 이유

  • 성능

    → 암호 해독 호출이 심하지 않고 간단한 해시만 사용한다.

  • 클라이언트 측 저장소

    → 쿠키와 같은 클라이언트에 실제 ID와 암호가 저장되지 않는다.

    → 토큰은 서버마다 다르며 다른 사이트에서는 재사용되지 않는다.

인가(Authorization)

  • Authorization은 유저가 요청하는 reuqest를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차이다.

  • 예를 들면 해당 유저는 고객 정보를 볼 수 있지만 수정할 수는 없는 경우가 있다.

  • Authorization도 JWT를 통해서 구현 될 수 있다.

    access token을 통해 해당 유저 정보를 얻을 수 있기 때문에 해당 유저가 가지고 있는 권한도 확인할 수 있다.

Authorization 절차

  1. Authentication 절차를 통해 access token을 생성한다. access token에는 유저 정보를 확인할 수 있는 정보가 들어가 있어야 한다 (예를 들어 user id).
  2. 유저가 request를 보낼때 access token을 첨부해서 보낸다.
  3. 서버에서는 유저가 보낸 access token을 복호화 한다.
  4. 복호화된 데이터를 통해 user id를 얻는다.
  5. user id를 사용해서 database에서 해당 유저의 권한(permission)을 확인하다.
  6. 유저가 충분한 권한을 가지고 있으면 해당 요청을 처리한다.
  7. 유저가 권한을 가지고 있지 않으면 Unauthorized Response(401) 혹은 다른 에러 코드를 보낸다.

프론트엔드와 백엔드의 통신

위에서 인증과 인가에 대한 내용을 학습했고, 그로 인해 어떻게 인증을 하고 권한을 인가하는가에 대해서 이해했다. 그래서 프론트엔드와 백엔드는 어떻게 통신하는데? 사실 이 질문에 답하기위해 위의 개념들을 학습하는게 아닐까?

  • 백엔드의 입장에서는 프론트엔드에서 보내 준 request.header에 담겨있는 access token을 받아서 그 권한이 있을 경우에 대한 로직, 없을 경우에 대한 로직을 구현하여 다시 response로 돌려보내준다.

이제 프론트엔드의 입장에서 한번 생각해보자.

  • 페이지를 이동할 때 마다, 브라우저를 끄고 켤 때 마다 매번 로그인을 할 수 없기 때문에 access token 을 주고 받아서 사용자 인증을 진행한다. 로그인을 하면 access token을 받아서 프론트엔드는 어딘가에 보관하고 있다가 사용자 정보에 필요한 api 에 해당 access token을 백엔드에 보내준다.
  • 어디에 저장할까? HTTP에서 학습했듯이 저장소에는 로컬, 쿠키, 세션 세가지 종류가 있다.
    • Local Storage: 해당 도메인에 영구 저장하고 싶을 때
    • Session Storage: 해당 도메인의, 한 세션에서만 저장하고 싶을 때. 창을 닫으면 data가 날라간다.
    • Cookie: 해당 도메인에 날짜를 설정하고 그 때까지만 저장하고 싶을 때

그럼 받아온 access token을 setItem 메소드를 사용하여 스토리지에 저장을 하고 request headerAuthorization 을 보내면 된다.

  1. 로컬 스토리지에 token 이 존재하는지 확인
  2. 있으면 request header의 Authrozation에 추가하기

이러한 과정을 거쳐서 프론트엔드와 백엔드는 서로 통신을 할 수 있다.

0개의 댓글