You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 00:05:55