blog/_posts/2023-08-01-auth.md
2023-08-01 14:09:27 +00:00

129 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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