280 lines
8.3 KiB
Python
280 lines
8.3 KiB
Python
"""
|
|
认证安全测试套件
|
|
|
|
测试范围:
|
|
1. 密码安全测试
|
|
2. 登录安全测试
|
|
3. 会话管理测试
|
|
4. 权限验证测试
|
|
5. 暴力破解防护测试
|
|
|
|
作者: 张翔
|
|
日期: 2026-04-01
|
|
"""
|
|
|
|
import pytest
|
|
import time
|
|
from api.auth_api import AuthAPI
|
|
from api.user_api import UserAPI
|
|
from config.settings import settings
|
|
|
|
|
|
@pytest.mark.security
|
|
@pytest.mark.asyncio
|
|
class TestAuthenticationSecurity:
|
|
"""认证安全测试类"""
|
|
|
|
async def test_password_strength_validation(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-01: 密码强度验证
|
|
|
|
验证点:
|
|
1. 弱密码被拒绝
|
|
2. 密码复杂度要求
|
|
3. 密码长度要求
|
|
"""
|
|
user_api = UserAPI(authenticated_client)
|
|
|
|
weak_passwords = [
|
|
"123456",
|
|
"password",
|
|
"admin",
|
|
"qwerty",
|
|
"abc123",
|
|
"111111",
|
|
"1234567890",
|
|
"password123"
|
|
]
|
|
|
|
for weak_pwd in weak_passwords:
|
|
user_data = {
|
|
"username": f"weak_pwd_test_{weak_pwd}",
|
|
"password": weak_pwd,
|
|
"email": f"weak_{weak_pwd}@test.com",
|
|
"phone": "13800138000",
|
|
"status": 1
|
|
}
|
|
|
|
response = await user_api.create_user(user_data)
|
|
|
|
assert response.status_code in [400, 422], \
|
|
f"弱密码 '{weak_pwd}' 应被拒绝"
|
|
|
|
async def test_password_hashing(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-02: 密码哈希验证
|
|
|
|
验证点:
|
|
1. 密码不以明文存储
|
|
2. 使用BCrypt或其他安全哈希
|
|
3. 每次哈希结果不同(盐值)
|
|
"""
|
|
user_api = UserAPI(authenticated_client)
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
user_data = {
|
|
"username": "hash_test_user",
|
|
"password": "Test123!@#",
|
|
"email": "hash_test@test.com",
|
|
"phone": "13800138000",
|
|
"status": 1
|
|
}
|
|
|
|
response = await user_api.create_user(user_data)
|
|
|
|
if response.status_code in [201, 200]:
|
|
user_id = response.json().get("id")
|
|
|
|
login_response = await auth_api.login(
|
|
user_data["username"],
|
|
user_data["password"]
|
|
)
|
|
|
|
assert login_response.status_code == 200, "密码验证失败"
|
|
|
|
await user_api.delete_user(user_id)
|
|
|
|
async def test_brute_force_protection(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-03: 暴力破解防护
|
|
|
|
验证点:
|
|
1. 多次失败登录被限制
|
|
2. 账户锁定机制
|
|
3. 登录失败提示不泄露信息
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
failed_attempts = 0
|
|
max_attempts = 10
|
|
|
|
for i in range(max_attempts):
|
|
response = await auth_api.login("admin", "wrong_password_123")
|
|
|
|
if response.status_code == 429:
|
|
assert True, "暴力破解防护生效"
|
|
return
|
|
elif response.status_code == 401:
|
|
failed_attempts += 1
|
|
else:
|
|
break
|
|
|
|
assert failed_attempts >= 3, \
|
|
"应实施暴力破解防护(至少3次失败后限制)"
|
|
|
|
async def test_session_timeout(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-04: 会话超时测试
|
|
|
|
验证点:
|
|
1. Token有过期时间
|
|
2. 过期Token无法使用
|
|
3. 会话自动失效
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
user_api = UserAPI(authenticated_client)
|
|
|
|
login_response = await auth_api.login("admin", "admin123")
|
|
token = login_response.json().get("token")
|
|
|
|
import jwt
|
|
decoded = jwt.decode(token, options={"verify_signature": False})
|
|
|
|
assert "exp" in decoded, "Token应包含过期时间"
|
|
|
|
exp_time = decoded["exp"]
|
|
current_time = time.time()
|
|
|
|
assert exp_time > current_time, "Token不应已过期"
|
|
assert exp_time - current_time <= 86400, "Token有效期不应超过24小时"
|
|
|
|
async def test_concurrent_session_handling(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-05: 并发会话处理
|
|
|
|
验证点:
|
|
1. 支持并发登录
|
|
2. 或限制并发会话数
|
|
3. 会话隔离
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
login_responses = []
|
|
|
|
for i in range(3):
|
|
response = await auth_api.login("admin", "admin123")
|
|
login_responses.append(response)
|
|
|
|
successful_logins = sum(
|
|
1 for r in login_responses if r.status_code == 200
|
|
)
|
|
|
|
assert successful_logins >= 1, "至少应支持一次登录"
|
|
|
|
async def test_logout_security(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-06: 登出安全测试
|
|
|
|
验证点:
|
|
1. 登出后Token失效
|
|
2. 无法重复使用登出Token
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
user_api = UserAPI(authenticated_client)
|
|
|
|
login_response = await auth_api.login("admin", "admin123")
|
|
token = login_response.json().get("token")
|
|
|
|
logout_response = await auth_api.logout()
|
|
|
|
if logout_response.status_code == 200:
|
|
client_with_old_token = authenticated_client.__class__(
|
|
base_url=settings.API_BASE_URL,
|
|
headers={"Authorization": f"Bearer {token}"}
|
|
)
|
|
|
|
user_api_old = UserAPI(client_with_old_token)
|
|
response = await user_api_old.get_users_by_page()
|
|
|
|
assert response.status_code in [401, 403], \
|
|
"登出后Token应失效"
|
|
|
|
async def test_password_change_security(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-07: 密码修改安全
|
|
|
|
验证点:
|
|
1. 需要旧密码验证
|
|
2. 新密码强度验证
|
|
3. 修改后需重新登录
|
|
"""
|
|
user_api = UserAPI(authenticated_client)
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
user_data = {
|
|
"username": "pwd_change_test",
|
|
"password": "OldPassword123!@#",
|
|
"email": "pwd_change@test.com",
|
|
"phone": "13800138000",
|
|
"status": 1
|
|
}
|
|
|
|
create_response = await user_api.create_user(user_data)
|
|
|
|
if create_response.status_code in [201, 200]:
|
|
user_id = create_response.json().get("id")
|
|
|
|
login_response = await auth_api.login(
|
|
user_data["username"],
|
|
user_data["password"]
|
|
)
|
|
|
|
assert login_response.status_code == 200
|
|
|
|
await user_api.delete_user(user_id)
|
|
|
|
async def test_account_lockout_mechanism(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-08: 账户锁定机制
|
|
|
|
验证点:
|
|
1. 多次失败后账户锁定
|
|
2. 锁定时间合理
|
|
3. 管理员可解锁
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
for i in range(5):
|
|
response = await auth_api.login("admin", "wrong_password")
|
|
|
|
if response.status_code == 423:
|
|
assert True, "账户锁定机制生效"
|
|
return
|
|
|
|
pytest.skip("系统未实现账户锁定机制")
|
|
|
|
async def test_login_csrf_protection(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-09: 登录CSRF防护
|
|
|
|
验证点:
|
|
1. 登录表单有CSRF Token
|
|
2. CSRF Token验证
|
|
"""
|
|
auth_api = AuthAPI(authenticated_client)
|
|
|
|
login_response = await auth_api.login("admin", "admin123")
|
|
|
|
assert login_response.status_code == 200
|
|
|
|
async def test_password_reset_security(self, authenticated_client):
|
|
"""
|
|
SEC-AUTH-10: 密码重置安全
|
|
|
|
验证点:
|
|
1. 需要邮箱验证
|
|
2. 重置链接有时效
|
|
3. 重置链接一次性使用
|
|
"""
|
|
pytest.skip("密码重置功能待实现或测试")
|