本地Flask对接Clover API授权遇SSL/400错误求解决方案
本地Flask+Clover OAuth授权的HTTPS问题解决办法
问题背景
我正在搭建本地运行的基础库存管理应用,用localhost部署Flask,实现Clover API授权时遇到问题。生成自签名证书启用HTTPS后,仍出现400错误、请求版本错误,Chrome里报ssl_protocol_error。代码几乎是Clover官方示例,仅添加了证书和调试内容。
我的代码
import flask import requests import config import os from OpenSSL import SSL ASSETS_DIR = os.path.dirname(os.path.abspath(__file__)) CLIENT_ID = config.CLIENT_ID CLIENT_SECRET = config.CLIENT_SECRET ENV = "https://sandbox.dev.clover.com" if not CLIENT_ID or not CLIENT_SECRET: raise ValueError("Set your CLIENT_ID and CLIENT_SECRET in config.py.") app = flask.Flask(__name__) context = SSL.Context(SSL.SSLv23_METHOD) context.use_privatekey_file('crt/key.pem') context.use_certificate_file('crt/certificate.pem') app.config['APPLICATION_ROOT'] = '/' app.config['PREFERRED_URL_SCHEME'] = 'https' @app.route("/", methods=["GET"]) def landing_page(): """Depending on whether `code` parameter is present, redirects to oauth_callback or to Clover merchant login.""" # A request from an authorized merchant includes a code in its query string. code = flask.request.args.get("code") # If the request doesn't include a code, redirect the merchant to Clover's # authorize endpoint. if not code: return flask.redirect("%s/oauth/authorize?client_id=%s" % (ENV, CLIENT_ID)) # If the request does include a code, redirect to this app's oauth_callback # in order to request an access_token. else: return flask.redirect("/oauth_callback?code=%s" % code) @app.route("/oauth_callback", methods=["GET"]) def oauth_callback(): """Uses `code` with CLIENT_ID and CLIENT_SECRET to request access_token.""" code = flask.request.args.get("code") # The merchant shouldn't reach this endpoint without a code, but just in case: if not code: return flask.redirect("/") # Use the code with your app ID and app secret to request an access_token. request = "%s/oauth/token?client_id=%s&client_secret=%s&code=%s" % (ENV, CLIENT_ID, CLIENT_SECRET, code) try: response = requests.get(request) response.raise_for_status() access_token = response.json().get("access_token") print("Access token: ", access_token) if access_token: return "Access token: " + access_token else: return "Could not retrieve access_token." except requests.RequestException as e: print("Request failed:", e) except Exception as e: print(e) ################################################################################ if __name__ == "__main__": app.run(host='0.0.0.0', port=5000, debug=True, ssl_context=context)
错误日志
127.0.0.1 - - [12/Feb/2024 19:09:57] code 400, message Bad request version ('**\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x9f\x1a\x1a\x00\x00\x00') 127.0.0.1 - - [12/Feb/2024 19:09:57] "\x16\x03\x01\x02\x0c\x01\x00\x02\x08\x03\x039Kmñx\x00=;G¢Ë¬\x95\x9c>)\x91ZSqîÝ%µÈ\x86OÞ\x1f>× \x96Büo\x08H4¡6ÆÎÈ´ö¡fÐÔ\x8552È\x00uÚ$ߪàss·\x00 **\x13\x01\x13\x02\x13\x03À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x01\x00\x01\x9f\x1a\x1a\x00\x00\x00" 400 -
解决办法
1. 简化SSL上下文配置
Flask内置支持直接传入证书文件路径,无需手动构建OpenSSL Context,这能避免协议版本不兼容问题:
# 删除原有的context创建代码 # context = SSL.Context(SSL.SSLv23_METHOD) # context.use_privatekey_file('crt/key.pem') # context.use_certificate_file('crt/certificate.pem') # 修改启动代码,直接传入证书元组 if __name__ == "__main__": app.run(host='0.0.0.0', port=5000, debug=True, ssl_context=('crt/certificate.pem', 'crt/key.pem'))
2. 重新生成合规的自签名证书
用OpenSSL生成符合现代TLS标准的证书,确保Common Name设置为localhost:
openssl req -x509 -newkey rsa:4096 -nodes -out crt/certificate.pem -keyout crt/key.pem -days 365
3. 核对Clover回调地址
在Clover开发者后台,将应用的OAuth回调地址设置为https://localhost:5000/oauth_callback,确保与代码中的路由完全匹配。
4. 本地开发的简便替代方案
用ngrok转发本地HTTP服务到HTTPS,绕开本地证书配置:
- 启动Flask的HTTP服务(移除ssl_context参数):
if __name__ == "__main__": app.run(host='0.0.0.0', port=5000, debug=True)
- 运行ngrok转发端口:
ngrok http 5000
- 将Clover后台的回调地址替换为ngrok提供的HTTPS地址(例如
https://xxxx-xx-xx-xx-xx.ngrok.io/oauth_callback)。
5. 指定兼容的SSL协议版本
如果坚持手动配置SSL Context,指定使用TLS 1.2或更高版本:
from OpenSSL import SSL context = SSL.Context(SSL.TLSv1_2_METHOD) # 使用现代TLS协议 context.use_privatekey_file('crt/key.pem') context.use_certificate_file('crt/certificate.pem')
内容的提问来源于stack exchange,提问作者jon
相关产品推荐
相关产品推荐

