test_redis_auth.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. # -*- coding: utf-8 -*-
  2. """验证 /store/passport/login 的 Redis 登录态:登录写入 → 鉴权通过 → 登出删除 → 鉴权失效。
  3. 前置:Java 服务运行在 18080(yoshop.auth.mode=redis)。
  4. """
  5. import json
  6. import socket
  7. import sys
  8. import requests
  9. BASE = "http://127.0.0.1:18080"
  10. REDIS = ("192.168.1.177", 6379, "@@Qinys12346..")
  11. PREFIX = "yoshop:store:token:"
  12. S = requests.Session()
  13. S.trust_env = False
  14. passed = failed = 0
  15. def check(name, cond, extra=""):
  16. global passed, failed
  17. if cond:
  18. passed += 1
  19. print(" [ok] %s" % name)
  20. else:
  21. failed += 1
  22. print(" [FAIL] %s %s" % (name, extra))
  23. # ---------------------------------------------------------------- 最小 Redis 客户端
  24. def resp(sock, *args):
  25. data = b"*%d\r\n" % len(args)
  26. for a in args:
  27. b = a.encode() if isinstance(a, str) else a
  28. data += b"$%d\r\n%s\r\n" % (len(b), b)
  29. sock.sendall(data)
  30. return read(sock)
  31. def read(sock):
  32. line = readline(sock)
  33. t, rest = line[:1], line[1:]
  34. if t in (b"+", b"-"):
  35. return rest.decode()
  36. if t == b":":
  37. return int(rest)
  38. if t == b"$":
  39. n = int(rest)
  40. if n == -1:
  41. return None
  42. buf = b""
  43. while len(buf) < n + 2:
  44. buf += sock.recv(n + 2 - len(buf))
  45. return buf[:n].decode()
  46. if t == b"*":
  47. n = int(rest)
  48. return None if n == -1 else [read(sock) for _ in range(n)]
  49. return line.decode(errors="replace")
  50. def readline(sock):
  51. buf = b""
  52. while not buf.endswith(b"\r\n"):
  53. c = sock.recv(1)
  54. if not c:
  55. return buf
  56. buf += c
  57. return buf[:-2]
  58. class Redis:
  59. def __enter__(self):
  60. self.s = socket.create_connection((REDIS[0], REDIS[1]), timeout=5)
  61. resp(self.s, "AUTH", REDIS[2])
  62. return self
  63. def __exit__(self, *a):
  64. self.s.close()
  65. def __call__(self, *args):
  66. return resp(self.s, *args)
  67. def login(username, password):
  68. return S.post(BASE + "/store/passport/login",
  69. headers={"Content-Type": "application/json"},
  70. data=json.dumps({"username": username, "password": password}),
  71. timeout=30).json()
  72. def api(token):
  73. headers = {"Access-Token": token} if token else {}
  74. return S.get(BASE + "/store/goods/list", headers=headers, timeout=30).json()
  75. def main():
  76. with Redis() as r:
  77. print("== 1. 登录 ==")
  78. res = login("redistest_admin", "123456")
  79. check("登录成功", res.get("status") == 200 and res.get("message") == "登录成功", json.dumps(res, ensure_ascii=False))
  80. token = (res.get("data") or {}).get("token")
  81. check("返回 token", isinstance(token, str) and len(token) == 32, str(token))
  82. if not token:
  83. return
  84. key = PREFIX + token
  85. print("== 2. Redis 落库 ==")
  86. raw = r("GET", key)
  87. check("key 存在", raw is not None, "key=%s" % key)
  88. ttl = r("TTL", key)
  89. check("TTL 为 7 天(604800,允许 5 秒误差)", isinstance(ttl, int) and 604795 <= ttl <= 604800, "ttl=%s" % ttl)
  90. try:
  91. val = json.loads(raw) if raw else {}
  92. # RedisConfig 的 value 序列化器是 Jackson2JsonRedisSerializer,
  93. # 写入 String 时会再包一层 JSON 编码(存储为 "{\"user\":...}"),
  94. # 这里解两层,兼容纯 JSON 与双层编码两种存法。
  95. if isinstance(val, str):
  96. val = json.loads(val)
  97. except Exception:
  98. val = {}
  99. check("value.user 有 store_user_id", isinstance(val.get("user"), dict) and "store_user_id" in val["user"],
  100. json.dumps(val, ensure_ascii=False)[:160])
  101. check("value.store_id 正确", val.get("store_id") == 10001, "store_id=%s" % val.get("store_id"))
  102. check("value.is_login 为 true", val.get("is_login") is True, "is_login=%r" % val.get("is_login"))
  103. print("== 3. 带 token 访问业务接口 ==")
  104. ok = api(token)
  105. check("/store/goods/list 返回 200", ok.get("status") == 200, json.dumps(ok, ensure_ascii=False)[:160])
  106. print("== 4. 无 token 应 401 ==")
  107. no = api(None)
  108. check("无 token 返回 401", no.get("status") == 401 and no.get("message") == "请先登录后再访问",
  109. json.dumps(no, ensure_ascii=False)[:160])
  110. print("== 5. 登出 ==")
  111. out = S.post(BASE + "/store/passport/logout", headers={"Access-Token": token}, timeout=30).json()
  112. check("登出返回 200/操作成功", out.get("status") == 200 and out.get("message") == "操作成功",
  113. json.dumps(out, ensure_ascii=False)[:160])
  114. print("== 6. Redis 已清除 ==")
  115. check("key 已删除", r("GET", key) is None, "key=%s" % key)
  116. check("TTL 为 -2", r("TTL", key) == -2, "ttl=%s" % r("TTL", key))
  117. print("== 7. 登出后 token 失效 ==")
  118. after = api(token)
  119. check("旧 token 返回 401", after.get("status") == 401, json.dumps(after, ensure_ascii=False)[:160])
  120. print("\n通过 %d / 失败 %d" % (passed, failed))
  121. return 1 if failed else 0
  122. if __name__ == "__main__":
  123. sys.exit(main())