Xác thực Token trong Tự động hóa API với Python: Khái niệm và Thực hành
Trong tự động hóa kiểm thử API, việc xử lý xác thực là một bước quan trọng. Token là một cơ chế xác thực phổ biến giúp quản lý phiên người dùng mà không cần gửi lại thông tin đăng nhập trong mỗi yêu cầu. Bài viết này sẽ đi sâu vào nguyên lý hoạt động của Token và hướng dẫn cách áp dụng nó trong các kịch bản tự động hóa API sử dụng Python.
I. Tổng quan về Token
1. Vai trò của Token
Token được sử dụng để xác minh danh tính người dùng sau lần đăng nhập đầu tiên, giúp giảm tải cho máy chủ bằng cách hạn chế các truy vấn cơ sở dữ liệu lặp đi lặp lại để kiểm tra thông tin đăng nhập. Điều này làm cho hệ thống trở nên mạnh mẽ và có khả năng mở rộng tốt hơn.
2. Định nghĩa Token
Token là một chuỗi ký tự được máy chủ tạo ra và gửi về cho client (ứng dụng khách) sau khi quá trình đăng nhập ban đầu thành công. Client sau đó sẽ lưu trữ và gửi Token này kèm theo mỗi yêu cầu truy cập tài nguyên cần xác thực, thay vì gửi lại tên người dùng và mật khẩu.
3. Nguyên lý hoạt động của Token
- Khi người dùng đăng nhập thành công, máy chủ tạo ra một Token duy nhất và lưu trữ nó (thường là trong cơ sở dữ liệu hoặc bộ nhớ cache).
- Token này được trả về cho client.
- Client nhận và lưu giữ Token (ví dụ: trong bộ nhớ, localStorage, hoặc cookie).
- Trong các yêu cầu API tiếp theo (không phải yêu cầu đăng nhập), client đính kèm Token này vào header hoặc body của yêu cầu gửi đến máy chủ.
- Máy chủ nhận yêu cầu, trích xuất Token và so sánh nó với Token đã lưu trữ.
- Nếu Token khớp, máy chủ xác định người dùng đã được xác thực và xử lý yêu cầu.
- Nếu Token không có hoặc không hợp lệ, yêu cầu bị từ chối hoặc người dùng được yêu cầu đăng nhập lại.
4. Ưu điểm của xác thực bằng Token
- Không trạng thái (Stateless): Máy chủ không cần lưu trữ thông tin phiên người dùng (session) cho mỗi client. Mọi thông tin cần thiết đều nằm trong Token, giúp máy chủ dễ dàng mở rộng và chịu tải tốt hơn.
- Khả năng tái sử dụng: Một Token có thể được sử dụng để xác thực trên nhiều nền tảng hoặc dịch vụ khác nhau, tạo điều kiện thuận lợi cho việc tích hợp với các ứng dụng bên thứ ba.
- Bảo mật: Giảm thiểu các rủi ro từ tấn công giả mạo yêu cầu liên miền (CSRF) vì không sử dụng cookie để quản lý phiên.
5. So sánh Token với Cookie và Session
Đối với các trang web nhỏ hoặc vừa chỉ cần xác thực người dùng và truy cập dữ liệu trong cơ sở dữ liệu của chính trang đó, cơ chế Session Cookies thường là đủ. Tuy nhiên, với các hệ thống lớn hơn, ứng dụng doanh nghiệp, hoặc các API cần xử lý lượng lớn yêu cầu từ nhiều client khác nhau (bao gồm cả các bên thứ ba và các miền khác nhau), Token rõ ràng là lựa chọn ưu việt hơn nhờ tính không trạng thái và khả năng mở rộng.
II. Thực hành xác thực Token trong Python
Để minh họa cách thức hoạt động của Token, chúng ta sẽ xem xét một kịch bản API đăng nhập và truy cập danh sách dự án. Giả sử API của chúng ta chạy trên http://127.0.0.1:8000.
1. Lấy Token sau khi đăng nhập
Đầu tiên, chúng ta sẽ gửi yêu cầu POST đến endpoint đăng nhập với thông tin tên người dùng và mật khẩu. API sẽ trả về một Token nếu đăng nhập thành công.
import requests
import json
# Endpoint đăng nhập
login_api_url = 'http://127.0.0.1:8000/api/auth/login'
# Thông tin đăng nhập
user_credentials = {
"username": "testuser",
"password": "securepassword123"
}
try:
# Gửi yêu cầu đăng nhập
login_response = requests.post(login_api_url, json=user_credentials)
login_response.raise_for_status() # Ném lỗi cho các mã trạng thái HTTP không thành công
response_data = login_response.json()
print("Phản hồi đăng nhập:")
print(json.dumps(response_data, indent=2))
# Trích xuất token từ phản hồi
authentication_token = response_data.get("token")
user_id = response_data.get("user_id")
username_returned = response_data.get("username")
if authentication_token:
print(f"\nToken nhận được: {authentication_token}")
print(f"ID người dùng: {user_id}")
print(f"Tên người dùng: {username_returned}")
else:
print("\nKhông nhận được token trong phản hồi.")
except requests.exceptions.RequestException as e:
print(f"Lỗi khi thực hiện yêu cầu đăng nhập: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Phản hồi lỗi: {e.response.text}")
Phản hồi từ máy chủ có thể trông như sau:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InZpdml0ZXN0IiwiZXhwIjoxNjk5NTY3ODkwLCJlbWFpbCI6InRlc3RAdGVzdC5jb20ifQ.AbcDEfgHIJKlMnO_PQRSTU_VWXyZ123456",
"user_id": 1,
"username": "testuser"
}
Sau khi lấy được Token, chúng ta cần sử dụng nó cho các yêu cầu tiếp theo.
2. Truy cập tài nguyên yêu cầu xác thực
Giả sử chúng ta có một API để lấy danh sách các dự án tại http://127.0.0.1:8000/api/projects/. Thử truy cập mà không có Token sẽ nhận được lỗi.
import requests
import json
# Endpoint danh sách dự án
projects_api_url = 'http://127.0.0.1:8000/api/projects/'
print("--- Thử truy cập không có xác thực ---")
try:
project_list_response_unauth = requests.get(projects_api_url)
project_list_response_unauth.raise_for_status()
print(json.dumps(project_list_response_unauth.json(), indent=2))
except requests.exceptions.RequestException as e:
print(f"Lỗi khi truy cập không xác thực: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Phản hồi lỗi: {e.response.text}")
Phản hồi khi không có Token thường là lỗi ủy quyền:
{
"detail": "Credentials were not provided."
}
Bây giờ, chúng ta sẽ sử dụng Token đã lấy ở bước 1 để truy cập danh sách dự án. Token cần được đính kèm vào Header của yêu cầu HTTP, thường là với tiền tố như Bearer. Tiền tố này do backend quy định, bạn nên tham khảo tài liệu API để biết chính xác.
import requests
import json
# Giả sử authentication_token đã được lấy từ bước đăng nhập trước đó
# Ví dụ: authentication_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InZpdml0ZXN0IiwiZXhwIjoxNjk5NTY3ODkwLCJlbWFpbCI6InRlc3RAdGVzdC5jb20ifQ.AbcDEfgHIJKlMnO_PQRSTU_VWXyZ123456"
# Để đảm bảo mã này chạy độc lập, chúng ta sẽ thực hiện lại bước đăng nhập để lấy token
login_api_url = 'http://127.0.0.1:8000/api/auth/login'
user_credentials = {
"username": "testuser",
"password": "securepassword123"
}
try:
login_response = requests.post(login_api_url, json=user_credentials)
login_response.raise_for_status()
authentication_token = login_response.json().get("token")
if not authentication_token:
raise ValueError("Không thể lấy token từ phản hồi đăng nhập.")
except requests.exceptions.RequestException as e:
print(f"Không thể lấy token do lỗi đăng nhập: {e}")
exit() # Thoát nếu không lấy được token
# Endpoint danh sách dự án
projects_api_url = 'http://127.0.0.1:8000/api/projects/'
# Chuẩn bị header với Token. Lưu ý khoảng trắng giữa tiền tố và token.
authorization_header_value = f"Bearer {authentication_token}"
request_headers = {
"Authorization": authorization_header_value,
"Content-Type": "application/json" # Thường nên thêm Content-Type
}
print("\n--- Truy cập với xác thực Token ---")
try:
# Gửi yêu cầu GET với header chứa Token
project_list_response_auth = requests.get(projects_api_url, headers=request_headers)
project_list_response_auth.raise_for_status() # Kiểm tra lỗi HTTP
print("Phản hồi danh sách dự án:")
print(json.dumps(project_list_response_auth.json(), indent=2))
except requests.exceptions.RequestException as e:
print(f"Lỗi khi truy cập có xác thực: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Phản hồi lỗi: {e.response.text}")
Phản hồi thành công sẽ hiển thị danh sách dự án:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Dự án kiểm thử tự động A",
"tester": "Alice"
},
{
"id": 2,
"name": "Dự án quản lý chất lượng B",
"tester": "Bob"
}
]
}