基于Django开发的Shopify应用卸载后访问报错问题
Shopify应用卸载后访问URL返回401未授权错误的解决方法
问题场景
基于shopify_django_app仓库开发的Shopify应用,安装后可正常运行,但从店铺卸载后访问应用URL会触发500内部服务器错误,核心错误为pyactiveresource.connection.UnauthorizedAccess: Response(code=401),提示无效API密钥或访问令牌。
错误日志
Internal Server Error: / Traceback (most recent call last): File "D:\app\venv\lib\site-packages\pyactiveresource\connection.py", line 286, in _open http_response = self._handle_error(self._urlopen(request)) File "D:\app\venv\lib\site-packages\pyactiveresource\connection.py", line 316, in _urlopen return urllib.request.urlopen(request, timeout=self.timeout) File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 216, in urlopen return opener.open(url, data, timeout) File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 525, in open response = meth(req, response) File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 634, in http_response response = self.parent.error( File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 563, in error return self._call_chain(*args) File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 496, in _call_chain result = func(*args) File "C:\Users\royal\miniconda3\lib\urllib\request.py", line 643, in http_error_default raise HTTPError(req.full_url, code, msg, hdrs, fp) urllib.error.HTTPError: HTTP Error 401: Unauthorized During handling of the above exception, another exception occurred: Traceback (most recent call last): File "D:\app\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner response = get_response(request) File "D:\app\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "D:\app\shopify_django_app\shopify_app\decorators.py", line 12, in wrapper return fn(request, *args, **kwargs) File "D:\app\shopify_django_app\home\views.py", line 16, in index products = shopify.Product.find() File "D:\app\venv\lib\site-packages\shopify\base.py", line 196, in find collection = super(ShopifyResource, cls).find(id_=id_, from_=from_, **kwargs) File "D:\app\venv\lib\site-packages\pyactiveresource\activeresource.py", line 386, in find return cls._find_every(from_=from_, **kwargs) File "D:\app\venv\lib\site-packages\pyactiveresource\activeresource.py", line 525, in _find_every response = cls.connection.get(path, cls.headers) File "D:\app\venv\lib\site-packages\pyactiveresource\connection.py", line 329, in get return self._open('GET', path, headers=headers) File "D:\app\venv\lib\site-packages\shopify\base.py", line 23, in _open self.response = super(ShopifyConnection, self)._open(*args, **kwargs) File "D:\app\venv\lib\site-packages\pyactiveresource\connection.py", line 288, in _open http_response = self._handle_error(err) File "D:\app\venv\lib\site-packages\pyactiveresource\connection.py", line 415, in _handle_error raise UnauthorizedAccess(err) pyactiveresource.connection.UnauthorizedAccess: Response(code=401, body="b'{\"errors\":\"[API] Invalid API key or access token (unrecognized login or wrong password)\"}'", headers={'Date': 'Fri, 11 Aug 2023 11:46:01 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'close', 'X-Sorting-Hat-PodId': '319', 'X-Sorting-Hat-ShopId': '72702755136', 'Referrer-Policy': 'origin-when-cross-origin', 'X-Frame-Options': 'DENY', 'X-ShopId': '72702755136', 'X-ShardId': '319', 'WWW-Authenticate': 'Basic Realm=\"Shopify API Authentication\"', 'Strict-Transport-Security': 'max-age=7889238', 'Server-Timing': 'processing;dur=29', 'X-Shopify-Stage': 'production', 'Content-Security-Policy': "default-src 'self' data: blob: 'unsafe-inline' 'unsafe-eval' https://* shopify-pos://*; block-all-mixed-content; child-src 'self' https://* shopify-pos://*; connect-src 'self' wss://* https://*; frame-ancestors 'none'; img-src 'self' data: blob: https:; script-src https://cdn.shopify.com https://cdn.shopifycdn.net https://checkout.shopifycs.com https://api.stripe.com https://mpsnare.iesnare.com https://appcenter.intuit.com https://www.paypal.com https://js.braintreegateway.com https://c.paypal.com https://maps.googleapis.com https://www.google-analytics.com https://v.shopify.com 'self' 'unsafe-inline' 'unsafe-eval'; upgrade-insecure-requests; report-uri /csp-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Fproducts&source%5Bsection%5D=admin_api&source%5Buuid%5D=b46c2afc-80f3-4566-90e3-0fe20c35d3ba", 'X-Content-Type-Options': 'nosniff', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', 'X-XSS-Protection': '1; mode=block; report=/xss-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Fproducts&source%5Bsection%5D=admin_api&source%5Buuid%5D=b46c2afc-80f3-4566-90e3-0fe20c35d3ba', 'X-Dc': 'gcp-asia-south1,gcp-us-central1,gcp-us-central1', 'X-Request-ID': 'b46c2afc-80f3-4566-90e3-0fe20c35d3ba', 'CF-Cache-Status': 'DYNAMIC', 'Report-To': '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=5ia78nHKzIhToOvDIQVkvrHwaxDNNvCQXSPxnqHQZ0n%2FosdCZTI2E9ACFETWfr0eyz4%2Bu7pi9JAdJWJCz5I4oMnj0AbooqopOxi5bxA%2B%2FhMkGOs2ivWCsIQfja10K7EnBQyEg%2BKb%2FuBsuw%3D%3D"}],"group":"cf-nel","max_age":604800}', 'NEL': '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}', 'Server': 'cloudflare', 'CF-RAY': '7f503c547e451798-MAA', 'alt-svc': 'h3=":443"; ma=86400'}, msg="Unauthorized")
问题原因
- 应用被卸载后,Shopify立刻失效对应店铺的访问令牌,但应用会话或数据库中仍存储着旧令牌
shop_login_required装饰器仅检查会话中是否存在shop和token,未验证令牌有效性,直接放行进入视图- 视图中调用
shopify.Product.find()时使用了失效令牌,触发Shopify API的401未授权错误,最终导致500内部服务器错误
解决方案
1. 增强shop_login_required装饰器的令牌验证
修改shopify_app/decorators.py中的装饰器,添加令牌有效性检查逻辑:
from django.shortcuts import redirect from django.conf import settings import shopify from pyactiveresource.connection import UnauthorizedAccess def shop_login_required(fn): def wrapper(request, *args, **kwargs): shop = request.session.get('shop') token = request.session.get('shopify_token') if shop and token: # 设置Shopify API上下文 shopify.ShopifyResource.set_site(f"https://{shop}/admin") shopify.ShopifyResource.set_oauth_credentials( api_key=settings.SHOPIFY_API_KEY, secret=settings.SHOPIFY_API_SECRET ) shopify.ShopifyResource.set_token(token) # 验证令牌有效性:调用无权限要求的Shop API try: shopify.Shop.current() except UnauthorizedAccess: # 令牌失效,清除会话并跳转授权页面 del request.session['shop'] del request.session['shopify_token'] request.session.save() return redirect(f"/login?shop={shop}") # 原有登录检查:会话无shop或token时跳转登录 if not shop or not token: return redirect(f"/login?shop={request.GET.get('shop', '')}") return fn(request, *args, **kwargs) return wrapper
2. 在视图中捕获API异常
修改home/views.py的视图函数,捕获UnauthorizedAccess异常,避免直接抛出500错误:
from django.shortcuts import render, redirect import shopify from pyactiveresource.connection import UnauthorizedAccess def index(request): try: products = shopify.Product.find() return render(request, 'home/index.html', {'products': products}) except UnauthorizedAccess: # 令牌失效,清除会话并跳转登录 del request.session['shop'] del request.session['shopify_token'] request.session.save() return redirect(f"/login?shop={request.session.get('shop', '')}")
3. 配置应用卸载Webhook(可选但推荐)
在Shopify后台为应用添加app/uninstalled Webhook,当应用被卸载时自动清除数据库中对应店铺的令牌记录:
- 进入Shopify后台的应用设置页面,添加Webhook,事件选择
App uninstalled,回调URL设置为Django应用中处理卸载事件的视图地址 - 在Django中创建对应的视图函数,接收Webhook请求后删除该店铺的令牌数据,避免后续请求使用无效令牌
内容的提问来源于stack exchange,提问作者royalsanga
相关产品推荐
相关产品推荐

