committed by
GitHub
18 changed files with 436 additions and 138 deletions
@ -0,0 +1,273 @@ |
|||
# 패스워드 해싱을 이용한 OAuth2, JWT 토큰을 사용하는 Bearer 인증 |
|||
|
|||
모든 보안 흐름을 구성했으므로, 이제 <abbr title="JSON Web Tokens">JWT</abbr> 토큰과 패스워드 해싱을 사용해 애플리케이션을 안전하게 만들 것입니다. |
|||
|
|||
이 코드는 실제로 애플리케이션에서 패스워드를 해싱하여 DB에 저장하는 등의 작업에 활용할 수 있습니다. |
|||
|
|||
이전 장에 이어서 시작해 봅시다. |
|||
|
|||
## JWT |
|||
|
|||
JWT 는 "JSON Web Tokens" 을 의미합니다. |
|||
|
|||
JSON 객체를 공백이 없는 긴 문자열로 인코딩하는 표준이며, 다음과 같은 형태입니다: |
|||
|
|||
``` |
|||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
|||
``` |
|||
|
|||
JWT는 암호화되지 않아 누구든지 토큰에서 정보를 복원할 수 있습니다. |
|||
|
|||
하지만 JWT는 서명되어 있습니다. 그래서 자신이 발급한 토큰을 받았을 때, 실제로 자신이 발급한게 맞는지 검증할 수 있습니다. |
|||
|
|||
만료 기간이 일주일인 토큰을 발행했다고 가정해 봅시다. 다음 날 사용자가 토큰을 가져왔을 때, 그 사용자가 시스템에 여전히 로그인되어 있다는 것을 알 수 있습니다. |
|||
|
|||
일주일 뒤에는 토큰이 만료될 것이고, 사용자는 인가되지 않아 새 토큰을 받기 위해 다시 로그인해야 할 것입니다. 만약 사용자(또는 제3자)가 토큰을 수정하거나 만료일을 변경하면, 서명이 일치하지 않기 때문에 알아챌 수 있을 것입니다. |
|||
|
|||
만약 JWT 토큰을 다뤄보고, 작동 방식도 알아보고 싶다면 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a> 을 확인하십시오. |
|||
|
|||
## `PyJWT` 설치 |
|||
|
|||
파이썬으로 JWT 토큰을 생성하고 검증하려면 `PyJWT` 를 설치해야 합니다. |
|||
|
|||
[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 `pyjwt` 를 설치하십시오: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pyjwt |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// info | 참고 |
|||
|
|||
RSA나 ECDSA 같은 전자 서명 알고리즘을 사용하려면, `pyjwt[crypto]`라는 암호화 라이브러리 의존성을 설치해야 합니다. |
|||
|
|||
더 자세한 내용은 <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT 설치</a> 에서 확인할 수 있습니다. |
|||
|
|||
/// |
|||
|
|||
## 패스워드 해싱 |
|||
|
|||
"해싱(Hashing)"은 어떤 내용(여기서는 패스워드)을 해석할 수 없는 일련의 바이트 집합(단순 문자열)으로 변환하는 것을 의미합니다. |
|||
|
|||
동일한 내용(똑같은 패스워드)을 해싱하면 동일한 문자열을 얻습니다. |
|||
|
|||
하지만 그 문자열을 다시 패스워드로 되돌릴 수는 없습니다. |
|||
|
|||
### 패스워드를 해싱하는 이유 |
|||
|
|||
데이터베이스를 탈취당하더라도, 침입자는 사용자의 평문 패스워드 대신 해시 값만 얻을 수 있습니다. |
|||
|
|||
따라서 침입자는 훔친 사용자 패스워드를 다른 시스템에서 활용할 수 없습니다. (대다수 사용자가 여러 시스템에서 동일한 패스워드를 사용하기 때문에 평문 패스워드가 유출되면 위험합니다.) |
|||
|
|||
## `passlib` 설치 |
|||
|
|||
PassLib는 패스워드 해시를 다루는 훌륭한 파이썬 패키지입니다. |
|||
|
|||
많은 안전한 해시 알고리즘과 도구들을 지원합니다. |
|||
|
|||
추천하는 알고리즘은 "Bcrypt"입니다. |
|||
|
|||
[가상환경](../../virtual-environments.md){.internal-link target=_blank} 을 만들고 활성화한 다음 PassLib와 Bcrypt를 설치하십시오: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "passlib[bcrypt]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip | 팁 |
|||
|
|||
`passlib`를 사용하여, **Django**, **Flask** 의 보안 플러그인이나 다른 도구로 생성한 패스워드를 읽을 수 있도록 설정할 수도 있습니다. |
|||
|
|||
예를 들자면, FastAPI 애플리케이션과 Django 애플리케이션이 같은 데이터베이스에서 데이터를 공유할 수 있습니다. 또는 같은 데이터베이스를 사용하여 Django 애플리케이션을 점진적으로 마이그레이션 할 수도 있습니다. |
|||
|
|||
그리고 사용자는 FastAPI 애플리케이션과 Django 애플리케이션에 동시에 로그인할 수 있습니다. |
|||
|
|||
/// |
|||
|
|||
## 패스워드의 해시와 검증 |
|||
|
|||
필요한 도구를 `passlib`에서 임포트합니다. |
|||
|
|||
PassLib "컨텍스트(context)"를 생성합니다. 이것은 패스워드를 해싱하고 검증하는데 사용합니다. |
|||
|
|||
/// tip | 팁 |
|||
|
|||
PassLib 컨텍스트는 다양한 해싱 알고리즘을 사용할 수 있는 기능을 제공하며, 더 이상 사용이 권장되지 않는 오래된 해싱 알고리즘을 검증하는 기능도 포함되어 있습니다. |
|||
|
|||
예를 들어, 다른 시스템(Django 같은)에서 생성한 패스워드를 읽고 검증할 수 있으며, 새로운 패스워드를 Bcrypt 같은 다른 알고리즘으로 해싱할 수도 있습니다. |
|||
|
|||
그리고 동시에 그런 모든 알고리즘과 호환성을 유지합니다. |
|||
|
|||
/// |
|||
|
|||
사용자로부터 받은 패스워드를 해싱하는 유틸리티 함수를 생성합니다. |
|||
|
|||
그리고 받은 패스워드가 저장된 해시와 일치하는지 검증하는 또 다른 유틸리티 함수도 생성합니다. |
|||
|
|||
그리고 사용자를 인증하고 반환하는 또 다른 함수도 생성합니다. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} |
|||
|
|||
/// note |
|||
|
|||
새로운 (가짜) 데이터베이스 `fake_users_db`를 확인하면, 해시 처리된 패스워드가 어떻게 생겼는지 볼 수 있습니다: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. |
|||
|
|||
/// |
|||
|
|||
## JWT 토큰 처리 |
|||
|
|||
설치된 모듈을 임포트 합니다. |
|||
|
|||
JWT 토큰 서명에 사용될 임의의 비밀키를 생성합니다. |
|||
|
|||
안전한 임의의 비밀키를 생성하려면 다음 명령어를 사용하십시오: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ openssl rand -hex 32 |
|||
|
|||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
그리고 생성한 비밀키를 복사해 변수 `SECRET_KEY`에 대입합니다. (이 예제의 변수 값을 그대로 사용하지 마십시오.) |
|||
|
|||
JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITHM` 을 생성하고 `"HS256"` 으로 설정합니다. |
|||
|
|||
토큰 만료 기간을 위한 변수를 생성합니다. |
|||
|
|||
응답을 위한 토큰 엔드포인트에 사용될 Pydantic 모델을 정의합니다. |
|||
|
|||
새 액세스 토큰을 생성하기 위한 유틸리티 함수를 생성합니다. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} |
|||
|
|||
## 의존성 수정 |
|||
|
|||
`get_current_user` 함수를 이전과 동일한 토큰을 받도록 수정하되, 이번에는 JWT 토큰을 사용하도록 합니다. |
|||
|
|||
받은 토큰을 디코딩하여 검증한 후 현재 사용자를 반환합니다. |
|||
|
|||
토큰이 유효하지 않다면 HTTP 오류를 반환합니다. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} |
|||
|
|||
## `/token` 경로 작업 수정 |
|||
|
|||
토큰의 만료 시각을 설정하기 위해 `timedelta` 를 생성합니다. |
|||
|
|||
실제 JWT 액세스 토큰을 생성하여 반환합니다. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} |
|||
|
|||
### JWT "주체(subject)" `sub`에 대한 기술 세부 사항 |
|||
|
|||
JWT 명세에 따르면 토큰의 주체를 포함하는 `sub`라는 키가 있습니다. |
|||
|
|||
사용 여부는 선택사항이지만, 사용자의 식별 정보를 저장할 수 있으므로 여기서는 이를 사용합니다. |
|||
|
|||
JWT는 사용자를 식별하고 사용자가 API를 직접 사용할 수 있도록 허용하는 것 외에도 다른 용도로 사용될 수도 있습니다. |
|||
|
|||
예를 들어 "자동차"나 "블로그 게시물"을 식별하는 데 사용할 수 있습니다. |
|||
|
|||
그리고 "자동차를 운전하다"나 "블로그 게시물을 수정하다"처럼 해당 엔터티에 대한 권한을 추가할 수 있습니다. |
|||
|
|||
그 후 이 JWT 토큰을 사용자(또는 봇)에게 제공하면, 그들은 계정을 따로 만들 필요 없이 API가 생성한 JWT 토큰만으로 작업(자동차 운전 또는 블로그 게시물 편집)을 수행할 수 있습니다. |
|||
|
|||
이러한 개념을 활용하면 JWT는 훨씬 더 복잡한 시나리오에도 사용할 수 있습니다. |
|||
|
|||
이 경우 여러 엔터티가 동일한 ID를 가질 수 있습니다. 예를 들어 foo라는 ID를 가진 사용자, 자동차, 블로그 게시물이 있을 수 있습니다. |
|||
|
|||
그래서 ID 충돌을 방지하기 위해, 사용자의 JWT 토큰을 생성할 때 접두사로 `sub` 키를 추가할 수 있습니다. 예를 들어 `username:` 을 붙이는 방식입니다. 이 예제에서는 `sub` 값이 `username:johndoe`이 될 수 있습니다. |
|||
|
|||
가장 중요한 점은 `sub` 키는 전체 애플리케이션에서 고유한 식별자가 되어야 하며 문자열이어야 한다는 점입니다. |
|||
|
|||
## 확인해봅시다 |
|||
|
|||
서버를 실행하고 문서로 이동하십시오: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
다음과 같은 사용자 인터페이스를 볼 수 있습니다: |
|||
|
|||
<img src="/img/tutorial/security/image07.png"> |
|||
|
|||
이전과 같은 방법으로 애플리케이션에 인증하십시오. |
|||
|
|||
다음 인증 정보를 사용하십시오: |
|||
|
|||
Username: `johndoe` |
|||
Password: `secret` |
|||
|
|||
/// check |
|||
|
|||
코드 어디에도 평문 패스워드 "`secret`" 이 없다는 점에 유의하십시오. 해시된 버전만 있습니다. |
|||
|
|||
/// |
|||
|
|||
<img src="/img/tutorial/security/image08.png"> |
|||
|
|||
`/users/me/` 를 호출하면 다음과 같은 응답을 얻을 수 있습니다: |
|||
|
|||
```JSON |
|||
{ |
|||
"username": "johndoe", |
|||
"email": "[email protected]", |
|||
"full_name": "John Doe", |
|||
"disabled": false |
|||
} |
|||
``` |
|||
|
|||
<img src="/img/tutorial/security/image09.png"> |
|||
|
|||
개발자 도구를 열어보면 전송된 데이터에 토큰만 포함된 것을 확인할 수 있습니다. 패스워드는 사용자를 인증하고 액세스 토큰을 받기 위한 첫 번째 요청에만 전송되며, 이후에는 전송되지 않습니다: |
|||
|
|||
<img src="/img/tutorial/security/image10.png"> |
|||
|
|||
/// note |
|||
|
|||
`Bearer `로 시작하는 `Authorization` 헤더에 주목하십시오. |
|||
|
|||
/// |
|||
|
|||
## `scopes` 의 고급 사용법 |
|||
|
|||
OAuth2는 "스코프(scopes)" 라는 개념을 갖고 있습니다. |
|||
|
|||
이를 사용하여 JWT 토큰에 특정 권한 집합을 추가할 수 있습니다. |
|||
|
|||
그 후 이 토큰을 사용자에게 직접 제공하거나 제3자에게 제공하여, 특정 제한사항 하에있는 API와 통신하도록 할 수 있습니다. |
|||
|
|||
**FastAPI** 에서의 사용 방법과 통합 방식은 **심화 사용자 안내서** 에서 자세히 배울 수 있습니다. |
|||
|
|||
## 요약 |
|||
|
|||
지금까지 살펴본 내용을 바탕으로, OAuth2와 JWT 같은 표준을 사용하여 안전한 **FastAPI** 애플리케이션을 만들 수 있습니다. |
|||
|
|||
거의 모든 프레임워크에서 보안 처리는 상당히 복잡한 주제입니다. |
|||
|
|||
이를 단순화하는 많은 패키지는 데이터 모델, 데이터베이스, 사용 가능한 기능들에 대해 여러 제약이 있습니다. 그리고 지나치게 단순화하는 일부 패키지들은 심각한 보안 결함을 가질 수도 있습니다. |
|||
|
|||
--- |
|||
|
|||
**FastAPI** 는 어떤 데이터베이스, 데이터 모델, 도구도 강요하지 않습니다. |
|||
|
|||
프로젝트에 가장 적합한 것을 선택할 수 있는 유연성을 제공합니다. |
|||
|
|||
그리고 `passlib` 와 `PyJWT` 처럼 잘 관리되고 널리 사용되는 패키지들을 바로 사용할 수 있습니다. **FastAPI** 는 외부 패키지 통합을 위해 복잡한 메커니즘이 필요하지 않기 때문입니다. |
|||
|
|||
그러나 유연성, 견고성, 보안성을 해치지 않으면서 과정을 단순화할 수 있는 도구들을 제공합니다. |
|||
|
|||
그리고 OAuth2와 같은 표준 프로토콜을 비교적 간단한 방법으로 구현하고 사용할 수 있습니다. |
|||
|
|||
더 세분화된 권한 체계를 위해 OAuth2의 "스코프"를 사용하는 방법은 **심화 사용자 안내서**에서 더 자세히 배울 수 있습니다. OAuth2의 스코프는 제3자 애플리케이션이 사용자를 대신해 그들의 API와 상호작용하도록 권한을 부여하기 위해, Facebook, Google, GitHub, Microsoft, Twitter 등의 많은 대형 인증 제공업체들이 사용하는 메커니즘입니다. |
@ -0,0 +1,17 @@ |
|||
# Triển khai FastAPI trên các Dịch vụ Cloud |
|||
|
|||
Bạn có thể sử dụng **bất kỳ nhà cung cấp dịch vụ cloud** nào để triển khai ứng dụng FastAPI của mình. |
|||
|
|||
Trong hầu hết các trường hợp, các nhà cung cấp dịch vụ cloud lớn đều có hướng dẫn triển khai FastAPI với họ. |
|||
|
|||
## Nhà cung cấp dịch vụ Cloud - Nhà tài trợ |
|||
Một vài nhà cung cấp dịch vụ cloud ✨ [**tài trợ cho FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, điều này giúp đảm bảo sự phát triển liên tục và khỏe mạnh của FastAPI và hệ sinh thái của nó. |
|||
|
|||
Thêm nữa, điều này cũng thể hiện cam kết thực sự của họ đối với FastAPI và **cộng đồng người dùng** (bạn), vì họ không chỉ muốn cung cấp cho bạn một **dịch vụ tốt** mà còn muốn đảm bảo rằng bạn có một **framework tốt và bền vững**, đó chính là FastAPI. 🙇 |
|||
|
|||
Bạn có thể thử các dịch vụ của họ và làm theo hướng dẫn của họ: |
|||
|
|||
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a> |
|||
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a> |
|||
* <a href="https://www.withcoherence.com/?utm_medium=advertising&utm_source=fastapi&utm_campaign=website" class="external-link" target="_blank">Coherence</a> |
|||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
@ -1,4 +1,4 @@ |
|||
# For mkdocstrings and tests |
|||
httpx >=0.23.0,<0.28.0 |
|||
# For linting and generating docs versions |
|||
ruff ==0.6.4 |
|||
ruff ==0.9.4 |
|||
|
Loading…
Reference in new issue