TIL 31. 인증과 인가 (Bcrypt, JWT)

문승준·2021년 9월 28일
0

Internet & Network

목록 보기
3/6
post-thumbnail

1. 인증과 인가


기본적인 회원가입과 로그인 절차

  1. 유저 회원 가입시 아이디와 비밀번호 입력

  2. 비밀번호는 암호화해서 DB에 저장

  3. 유저 로그인시 아이디와 비밀번호 입력

  4. 입력된 비밀번호를 암호화하여 DB에 저장 중인 유저 비밀번호와 비교

  5. 일치하면 access_token 을 생성해서 클라이언트에 전송

  6. 로그인에 성공한 유저는 이후 request header에 access_token을 첨부해서 보낸다. (Authorization)

인증 (Authentication)

시스템에 접근시, 등록된 사용자인지 여부를 확인하는것.

로그인시 아이디, 비밀번호 등 고유한 값으로 유저의 identification을 확인하는 절차

인가 (Authorization)

시스템 접근 후, 인증된 사용자에게 특정 권한을 부여하는 것.

사용자 등급 및 권한에 따라 기능 사용을 제한 할 수 있다.


2. 비밀번호 암호화 (bcrypt)


2-1. 비밀번호 암호화의 필요성

개인정보보호법에 따라 비밀번호, 바이오정보, 주민번호와 같은 개인정보는 암호화해서 관리해야한다.

암호화란 개인정보가 유출, 노출되더라도 그 내용 확인을 어렵게 하는 보안 기술이다.

방법 1. 해싱(난독화)하여 DB에 저장한다.

방법 2. SSL을 적용하여 암호화한다. (HTTPS)

  • 비밀번호 분실시 매번 재설정하는 이유는 서버에서도 원래 비밀번호를 알아낼 수 없기 때문이다.

  • 암호화된 것은 복원이 어렵기 때문에 로그인시 입력된 비밀번호를 암호화해서 DB 데이터와 비교하는 것이다.

2-2. 단방향 해시 함수

  • 본래 해시함수란 빠른 자료의 검색, 데이터의 위변조 체크를 위해 쓰인다.

  • 단방향 해시함수는 복원이 불가능하여 암호학적으로 사용한다.

  • SHA-256, (MD5, SHA-1 은 보안이 취약) 등이 있다.

  • 최종 암호화된 텍스트를 Digest라고 한다.

해시 함수란?

  • 임의의 길이의 데이터를 고정된 길이의 데이터로 반환시켜주는 함수이다.
  • 입력값의 길이가 달라도 출력값은 언제나 고정된 길이로 반환한다.
  • 동일한 값이 입력되면 언제나 동일한 출력 값을 보장한다.
  • 해시함수는 암호학적 해시 함수와 비 암호학적 해시함수로 구분된다.

2-3. 단방향 해시의 단점과 해결방법

  • 해시함수의 빠른 처리 속도로 인해 Rainbow Table Attack에 노출될 수 있다.

    → 전처리된 Digest를 많이 확보해서 Rainbow Table을 만들고 비교할 수있다.

Salting

  • 비밀번호에 임의로 생성한 문자열을 합쳐서 해싱한다.
  • 비교를 하기 위해 Salt값과 해시값을 함께 저장해야 한다.

Key Stretching

  • Salt값을 더한 상태에서 해싱을 여러번 반복해 원본 값을 찾기 더 어렵게 만드는 것.

2-4. bcrypt

bcrypt란?

salting & key stretching 해주는 대표적 라이브러리

다양한 언어를 지원하며 사용이 간편

최종 해싱된 값(Digest)에 알고리즘 종류, 반복횟수, 솔팅값, 해시값을 아래와 같이 보관한다.

bcrypt 사용방법

  • bcrypt 라이브러리를 설치하고 import한다.

  • 암호화할 passwordgensalt() 를 넣어서 해싱한다.

password = "123dsaS!!"

hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
# TypeError("Unicode-objects must be encoded before hashing")

hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
# 인코딩(바이트화)해야 암호화가 가능하다.
  • 암호화된 hashed_password 는 인코딩된 상태의 bytes 타입이어서 다시 decode해서 DB에 넣어준다.
hashed_password = hashed_password.decode('utf-8')

# 다시 디코딩하여 string 타입으로 만든다.
  • 입력된 비밀번호와 암호화된 비밀번호가 일치하는지 비교한다.
bcrypt.checkpw(input_password.encode('utf-8'), hashed_password.encode('utf-8'))

# 인코딩된 상태로 함수에 넣어야 비교가 가능하다.
  • 파이썬의 문자열 인코딩
    문자 인코딩이란 사용자가 입력한 문자나 기호들을 컴퓨터가 이용할 수 있는 신호로 만드는 것이며, 유니코드 문자열을 UTF-8, ASCII, EUC-KR 같은 byte 형태로 변환하는 것이다.

    유니코드는 국제표준 문자표이며, UTF-8은 유니코드를 사용한 인코딩 방식이다.

    파이썬의 문자열은 모두 유니코드이며 Python2는 ASCII를 , Python3는 UTF-8을 디폴트로 문자를 인코딩한다.

3. 토큰 기반 인증 시스템


3-1. 인증 후 인가 절차

  1. 인증(Authentication) 절차를 통해  access token 생성한다. 이 토큰에는 user id와 같이 유저를 구별할 수 있는 정보가 들어가야하지만 개인정보는 포함하면 안된다.

  2. 인증을 받은 유저는 request를 보낼 때 request headerAuthorization라는 이름으로 token 정보를 담아서 보낸다.

  3. 서버는 request에 담긴 token 정보를 복호화하여 우리가 발급한 token이 맞는지 확인하고 user id 등 정보를 확인한다.

  4. token에서 얻은 user id를 사용해서 DB에서 해당 유저의 권한(permission)을 확인한다.

  5. 유저가 해당 요청에 대한 권한을 가지고 있으면 요청을 처리한다.

  6. 유저가 권한이 없으면 Unauthorized Response(401) 또는 다른 에러 코드를 보낸다.


3-2. 서버 or 토큰 기반 인증

서버 기반 인증

기존의 웹 / 모바일 인증 시스템은 서버측에서 유저 정보를 기억하고 있는 방법이었다. 이 세션을 생성하고 유지하기 위해 세션 저장소(메모리, 디스크, 데이터베이스 시스템 등)가 필요하였다.

유저의 수가 폭발적으로 늘어나면 이러한 서버 기반 인증은 확장성 문제에 부딪히게 되었다.

분산된 서버 컴퓨터를 운영하며 세션을 사용하는 것은 매우 복잡한 과정을 필요로 하기 때문이다.

CORS 정책 : Cross-Origin Resource Sharing

브라우저를 통한 통신에는 CORS 정책이 적용되는데, 다른 도메인간 리소스 요청에 제한을 두는 것이다.

웹에서 세션 관리에 자주 사용되는 쿠키 또한 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 있다.

따라서 쿠키를 여러 도메인에서 관리하는 것도 복잡하고 번거로울 수 있다.

토큰 기반 시스템

인증할때 서버측에서 생성된 토큰은 서버가 아닌 클라이언트 측에 저장한다.

따라서 서버는 로그인 상태를 기억할 필요가 없으며 서버를 확장하기에 적합한 환경이다.

즉, 토큰 기반 시스템은 Stateless(무상태) 하고 Scalability(확장성) 이 있다.

Extensibility (확장성)

토큰을 이용한 로그인 정보는 더 넓은 분야에서 사용될 수 있다.

토큰을 발급할때 선택적인 권한을 부여해서 소셜 로그인이 가능하다.

ex) 카톡 계정으로 로그인한 사이트에서 카톡 프로필 정보는 가져올 수 있어도 카톡 메시지를 보낼 순 없다.

이처럼 여러 디바이스 호환과 다양한 서비스를 지원하기 위해 토큰을 활용할 수 있다. CORS 정책과 관련해서는 서버 응답의 헤더 부분에 Access-Control-Allow-Origin 내용을 추가해주어야 한다.


4. JWT (JSON Web Token)


4-1. JWT의 작동 원리

  • JWT는 인증에 필요한 정보들을 암호화시킨 토큰이다.

3. 토큰 발급 : 유저의 고유 ID값과 기타 정보를 Payload에 넣고 유효기간을 설정한다.

→ 서버측의 SECRET KEY를 이용해 발급한다.

5. 데이터 요청: 서버에 요청할때 Access Token(JWT)을 HTTP Header에 넣어서 보낸다.

6. 토큰 검증 : 해당 토큰의 Signature를 SECRET KEY로 복호화하고 조작여부와 유효기간을 확인한다.

7. 요청에 응답 : 검증이 완료되면 Payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져온다.

4-2. JWT 구조

  • 구조는 Header.Payload.Signature 으로 이루어져 있다.

  • Header : 토큰의 타입(typ)과 해시 알고리즘(alg) 정보 → 인코딩해서 전달

  • Payload : 공개/비공개 클레임 (일반적으로 유저의 고유 ID값, 유효기간) → 인코딩해서 전달

  • Signature : JWT가 원본이라는 것을 확인 → SECRET KEY를 더하고 암호화해서 전달

4-3. JWT 사용방법 (Python 기준)

  • pyjwt 라이브러리를 설치하고 jwtimport 한다.

  • 유저 ID, SECRET_KEY, 알고리즘 정보를 더해 토큰 발급하기

SECRET_KEY = 'afsf34hjke1rh2j' # '랜덤한 조합의 키'

access_token = jwt.encode({'id' : user.id}, SECRET_KEY, algorithm = 'HS256')

print(access_token)
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MX0.-xXA0iKB4mVNvWLYFtt2xNiYkFpObF54J9lj2RwduAI'
  • 토큰을 받아서 해당 유저가 맞는지 검증하기 (SECRET_KEY을 알아야 디코딩 가능)
header = jwt.decode(access_token, SECRET_KEY, algorithm = 'HS256')

print(header)
{'id': 1}      # 해당 유저가 맞는지 검증가능

자세한 예제 코드는 Instagram clone project에서,,

4-4. JWT 장단점

  • 서버는 검증만 하면되기 때문에 추가 저장소가 필요없고 stateless하며 scalability 하다. (서버의 확장, 유지, 보수가 용이)

  • 소셜 로그인 처럼 다른 인증 시스템에 접근할 수 있는 등 확장성이 뛰어나다.

  • 반면, 한번 발급된 JWT는 유효기간까지 계속 사용이 가능해서 주의해야한다.

    • 기존의 Access Token의 유효기간을 짧게하고 Refresh Token을 새로 발급한다.
      → 추후 Oauth2 공부예정
  • 디코딩만 하면 누구나 Payload 정보를 알 수 있기에 입력 가능한 내용이 제한적이다.

  • 세션/쿠키 방식에 비해 JWT의 길이가 긴편이다. 자칫 서버의 자원낭비가 발생할 수도 있다.


  • 참고목록

인증과 인가

토큰 기반 인증에 대한 소개

쉽게 알아보는 서버 인증(세션/쿠키, JWT)

파이썬의 문자 인코딩 이해하기

profile
개발자가 될 팔자

0개의 댓글