Mayx's Home Page https://mabbs.github.io
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
6.9 KiB

  1. ---
  2. layout: post
  3. title: 如何让Python脚本接收OAuth2.0的Code?
  4. tags: [Python, 服务器, OAuth]
  5. ---
  6. 越简单,就越复杂。<!--more-->
  7. # 起因
  8. 最近我在写一个在Windows上运行的Python脚本,需要做一个类似于第三方登录的功能。一般来说像这种东西都是使用的OAuth2.0来做认证,我接入的平台没有悬念的也是使用的这个东西,其中的重点就是获取到第三方网站的授权码Code,然后用它换取用户信息。于是我就在想怎么做比较好呢?
  9. # 解决方法
  10. ## 1. 使用URI Scheme
  11. 我看VSCode在实现类似功能的时候似乎用到了URI Scheme,就是在系统中注册一个伪协议,然后通过这个伪协议来调用程序实现获取Code,方法很简单,首先先在注册表里导入:
  12. ```ini
  13. Windows Registry Editor Version 5.00
  14. [HKEY_CLASSES_ROOT\mayx]
  15. "URL Protocol"="D:\\mayx.exe"
  16. @="MayxProtocol"
  17. [HKEY_CLASSES_ROOT\mayx\DefaultIcon]
  18. @="D:\\mayx.exe,1"
  19. [HKEY_CLASSES_ROOT\mayx\shell]
  20. [HKEY_CLASSES_ROOT\mayx\shell\open]
  21. [HKEY_CLASSES_ROOT\mayx\shell\open\command]
  22. @="\"D:\\mayx.exe\" \"%1\""
  23. ```
  24. 然后编写一个Python脚本并用Pyinstaller打包:
  25. ```python
  26. import sys
  27. print(sys.argv[1])
  28. ```
  29. 最后将回调地址填为“mayx://get”,这样认证完成之后就会像这样调用程序:“D:\mayx.exe mayx://get?code=something”,再用urllib简单做个解析就完成了。
  30. 这样看起来是不是非常的简单?看起来确实是挺简单的,可惜坑比较大,第一个是像上述这样的注册表有些杀毒软件会拒绝导入,因为像“shell\open\command”这种东西被病毒啥的滥用的地方太多了,除非软件通过了数字签名或者某些认证,否则正常情况下根本没法导入。二是不是所有的第三方平台都支持伪协议的方式调用,不过我试了一下我用的那个平台倒是支持😂,除了杀毒软件比较讨厌之外其他的倒还好。
  31. ## 2. 使用http服务监听
  32. 很多跨平台的程序,像有些主要在Linux上运行的某些程序就喜欢在获取Code的时候启动一个简单的web服务。其实用这个方法的话可能还更方便一点,不容易出问题。
  33. ### 使用Flask实现
  34. 最开始,我想着要不然就用Flask来实现这个功能吧,实现起来也简单,写起来就几行:
  35. ```python
  36. from flask import Flask, request
  37. code = ""
  38. app = Flask(__name__)
  39. @app.route('/getcode')
  40. def get():
  41. global code
  42. code = request.args.get("code")
  43. shutdown = request.environ["werkzeug.server.shutdown"]
  44. shutdown()
  45. return "OK"
  46. app.run(host="127.0.0.1",port=8000)
  47. print(code)
  48. ```
  49. 看起来也确实很简单,功能实现的也很完美,但是有个问题是总感觉用Flask做这么简单的事情实在是大材小用,打包成程序也要占不少空间,另外就是这个方法已经被弃用了,感觉就是很不爽。那要说不爽的话怎么才爽呢?
  50. ### 使用socket实现
  51. 我想起来之前期末时写的期末作业[socket-bbs](https://github.com/Mabbs/socket-bbs),这就是用socket实现的一个简单的论坛,那我这么简单的功能想来用socket实现不是很好吗?于是就写了一个:(这段代码没有测试,因为这个方法不能用所以把代码删了,这是重新做的)
  52. ```python
  53. import socket
  54. import urllib.parse
  55. server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #打开一个网络连接
  56. server.bind(('127.0.0.1',8000)) #绑定要监听的端口
  57. server.listen(5) # 设置最大的连接数量为5
  58. code = ""
  59. while True:
  60. sock, addr = server.accept() # 建立客户端连接
  61. data = sock.recv(8192).decode('utf-8').split('\r\n')#接收TCP数据,数据以字符串的形式返还
  62. if not data[0]:
  63. sock.close() # 关闭连接
  64. continue
  65. url = urllib.parse.urlparse(data[0].split()[1])
  66. if url.path == '/getcode':
  67. query = urllib.parse.parse_qs(self.data)
  68. code = query["code"][0]
  69. sock.send(("HTTP/1.0 200 OK" + '\r\n').encode('utf-8'))
  70. sock.send(("Content-Type: text/html; charset=utf-8" + '\r\n').encode('utf-8'))
  71. sock.send('\r\n'.encode('utf-8'))
  72. sock.send("OK".encode('utf-8')) #发送TCP数据
  73. sock.close() # 关闭连接
  74. break
  75. else:
  76. sock.send(("HTTP/1.0 404 Not Found" + '\r\n').encode('utf-8'))
  77. sock.send(("Content-Type: text/html; charset=utf-8" + '\r\n').encode('utf-8'))
  78. sock.send('\r\n'.encode('utf-8'))
  79. sock.send("Not Found".encode('utf-8')) #发送TCP数据
  80. sock.close() # 关闭连接
  81. print(code)
  82. ```
  83. 看起来是不是复杂多了?其实大多数时候工作的好像挺正常的,但是不知道为什么莫名其妙第一次访问的时候会卡住,改了半天头都大了,于是就只好放弃这种方法了……
  84. ### 使用http.server实现
  85. 这时候我突然想到我平时用来传文件用的python自带的模块http.server,既然这个模块是自带的,大小应该大不到哪去吧?随后我就写了一个:
  86. ```python
  87. from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
  88. import urllib.parse
  89. code = ""
  90. class Resquest(BaseHTTPRequestHandler):
  91. timeout = 5
  92. def do_GET(self):
  93. url = urllib.parse.urlparse(self.path)
  94. if url.path == "/getuser":
  95. self.send_response(200)
  96. self.send_header("Content-type", "text/html") # 设置服务器响应头
  97. code = urllib.parse.parse_qs(url.query)["code"][0]
  98. buf = '''OK'''
  99. self.wfile.write(buf.encode())
  100. self.server.shutdown()
  101. else:
  102. self.send_response(404)
  103. self.send_header("Content-type", "text/html") # 设置服务器响应头
  104. self.end_headers()
  105. buf = '''Not Found'''
  106. self.wfile.write(buf.encode())
  107. host = ("127.0.0.1", 8000)
  108. server = ThreadingHTTPServer(host, Resquest)
  109. print("Starting server, listen at: %s:%s" % host)
  110. server.serve_forever()
  111. server.socket.close()
  112. print(code)
  113. ```
  114. 最后打包试了一下,功能正常,虽然还是大了点,不过比Flask小😁。其实我在写这个代码的时候,最开始不知道要在最后加上`server.socket.close()`,一开始写的时候总是遇到程序执行完之前端口必定不会释放,令人很烦,直接搜也没什么好的结果,不过问了下ChatGPT立马就解决了🤣,它说:
  115. > 当你调用`server.shutdown()`来停止服务器时,它会停止接收新的连接,并关闭已有的连接。但是,由于Python的socket库的设计,socket对象并不会立即释放端口。相反,它会在一段时间内处于TIME_WAIT状态,以确保在网络中所有挂起的数据都被正确传递或丢弃。这是一种网络协议的要求,称为"TCP TIME_WAIT"状态。
  116. AI还真是方便啊……
  117. # 感想
  118. 解决这么一个小问题却一时半会拿不下最合适的方案,这难道就是了解太多的副作用吗😝,不过最终来看还是AI厉害,一下子就解决了我的问题。

Powered by TurnKey Linux.