假如你是一家餐厅的老板:顾客进门,服务员先递上一张取号牌;你拿着号牌去商场总台核实会员身份;顾客坐定后出示会员卡,你用密码本验证真伪;结账时,你在收银台报一笔账目,收银台送回一张盖着防伪章的确认单,你核对无误才笑着说“欢迎下次光临”。微信小程序的登录与支付,就是把这套“先验人、再收钱”的信任流程搬进了数字世界。前端、后端、微信登录服务、微信支付服务四方协作,靠 code、session_key、sign、回调这些“暗号”建立信任。这篇文章会带你走一遍完整链路:从 wx.login 取 code,到后端换取会话钥匙;从用户授权解密资料,到支付下单、签名、验签;最后聊聊登录态维护和风控这些“安保措施”。读完你会发现,登录支付接口集成的本质,是设计一套滴水不漏的信任传递机制。

岗位关联

对 AI 时代的前端与全栈开发者而言,理解这套“可信会话”的闭环设计,是构建 AI 客服鉴权、支付风控、用户画像等智能应用的基础功。

目录
  1. 可视化演示
  2. 登录流程基础
  3. 微信登录授权
  4. 用户信息获取
  5. 支付统一下单
  6. 签名生成机制
  7. 回调验签处理
  8. 登录态维护
  9. 支付安全防控
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

登录流程基础

餐厅迎宾的流程从来不是把名片一扔就完事:服务员先发一张取号牌,顾客入座后,你还要拿着这张牌去商场总台核实会员信息。小程序登录也是同样的两段式——先用 wx.login() 向微信索要一个临时凭证 code,再把 code 交给后端,由后端去微信服务器换取真正的“会话钥匙”。

代码上的写法非常简洁:前端调用 wx.login() 后拿到 code,通过 HTTPS 发给自己的后端;后端拿着 code、AppID、AppSecret 三个参数,请求微信的 code2Session 接口。微信服务器核对无误后,返回两个关键字段:session_key 和 openid。小程序本身的代码里永远不出现 AppSecret,因为它就像老板的指纹,只能由后端保存。

两段式登录的精髓就在这“换”字上:前端不知道暗号的含义,后端才有资格对暗号。谁该掌握什么秘密,边界清晰得像餐厅的前厅与后厨。

session_key, openid=code2Session(code, appid, secret)\text{session\_key},\ \text{openid} = \text{code2Session}(\text{code},\ \text{appid},\ \text{secret})
这是登录阶段最核心的一次远程调用:小程序不直接碰密钥,由后端用 code 换回会话凭据,相当于顾客在前台取号、老板到总台核档。
  1. 输入 codewx.login 返回的临时凭证,相当于取号牌上的号码,一次性使用且 5 分钟内有效。
    code\text{code}
  2. 输入 appid 与 secret小程序唯一标识和后端持有的签名密钥,相当于餐厅营业执照号和老板指纹,用来证明“老板本人来核档”。
    appid, secret\text{appid},\ \text{secret}
  3. 输出 session_key会话密钥,相当于会员档案里的密码本,后续解密用户信息全靠它,绝不能下发给前端。
    session_key\text{session\_key}
  4. 输出 openid用户在小程序体系内的唯一 ID,相当于微信商场发给顾客的专属会员卡号。
    openid\text{openid}

微信登录授权

取号牌若永不失效,餐厅早就乱套了。微信给 code 也定了规矩:5 分钟有效期,且只能用一次。前端在 wx.login 的成功回调里拿到 code,必须立刻传给后端;一旦过期或用完,微信服务器就会礼貌地返回一个错误,要求你重新取号。

后端拿到 code 后,调用 code2Session 接口。这里要特别注意:AppSecret 只能放在后端环境变量或配置中心,绝不能写进小程序前端代码,否则任何人反编译小程序都能冒充老板。返回的 session_key 同样留在后端,并按 openid 组织存储。此时登录的“种子”已经埋下:小程序侧还没有任何身份标识,登录态尚未建立。

好取号牌的设计,是既让顾客方便,又让捡到牌的人无计可施。code 的“短命”就是微信生态的第一道防盗门。

code→一次性使用或超时后∅\text{code} \xrightarrow{\text{一次性使用或超时后}} \varnothing
code 的生命周期很短,设计目的就是降低泄露风险:就算取号牌被抢走,5 分钟后也变废纸。
  1. code 生命周期wx.login 每次返回全新 code,5 分钟过期,且只能使用一次。
    codet=0→Δt>5min∅\text{code}_{t=0} \xrightarrow{\Delta t > 5\text{min}} \varnothing
  2. 一次性语义同一 code 被二次请求 code2Session 时直接报错,防止旧凭证被重放。
    code∈used⇒error\text{code} \in \text{used} \Rightarrow \text{error}

用户信息获取

顾客坐定后,服务员会掏出会员卡说“请确认授权”。在小程序里,这个动作对应 wx.getUserProfile():它必须在用户主动点击按钮后调用,弹出授权弹窗,让用户确认是否允许小程序读取头像昵称等基础资料。

用户同意后,微信并不会直接返回明文资料,而是返回 encryptedData 和 iv 两段密文——像是把会员卡信息放进一个加密信封。信封要打开,必须用之前换回的 session_key 作为密码本,由后端执行 AES 解密。小程序把 encryptedData 和 iv 发给后端,后端用 session_key 解密得到 JSON 形式的用户信息,再把需要展示给前端的结果返回。

这一节其实在讲“最小授权”:用户只允许你看他的基础资料,而你用密码本验明正身后,既不把密码本给别人,也不把整本档案复印出去,信任就这样一层层建立起来。

userInfo=AESDecrypt(encryptedData, session_key, iv)\text{userInfo} = \text{AESDecrypt}\left(\text{encryptedData},\ \text{session\_key},\ \text{iv}\right)
解密动作发生在后端而不是前端,等于老板亲自拆信封、验身份,顾客拿到的只是老板转述的“欢迎语”。
  1. encryptedData微信返回的加密用户数据串,相当于贴了封条的会员卡复印件。
    encryptedData\text{encryptedData}
  2. session_key 与 ivsession_key 是密码本,iv 是初始向量,二者配合才能正确解开 AES 密文。
    session_key, iv\text{session\_key},\ \text{iv}
  3. userInfo 输出解密后得到包含 nickName、avatarUrl、unionid 等字段的用户信息对象。
    userInfo={nickName, avatarUrl, unionid}\text{userInfo} = \{\text{nickName},\ \text{avatarUrl},\ \text{unionid}\}

支付统一下单

顾客吃完了,你说“您好,这儿结账”。在小程序里,结账可不是前端直接扫码付款,而是先由后端到“微信支付”这台收银机上报一笔订单,拿到一张盖好编号的预支付小票(prepay_id),再交给前端去发起收银台付款。

统一下单是支付的起点。后端调用微信支付的统一下单接口,需要组织一长串参数:appid、mch_id(商户号)、out_trade_no(商户订单号)、total_fee(金额,单位是分)、notify_url(回调地址)、body(商品描述)等等。最关键的是,这些参数必须带上一个由签名算法生成的 sign,收银机验完签名才会受理。受理成功后返回 prepay_id,后端再用它生成小程序端调起支付所需的 paySign,前端拿到后调用 wx.requestPayment。

老板把菜单递给收银台,收银台在菜单上盖了“预受理”章又递回来——这时候顾客才被请到收银台前付款。收银秩序,一目了然。

prepay_id=unifiedOrder(params∪{sign})\text{prepay\_id} = \text{unifiedOrder}(\text{params} \cup \{\text{sign}\})
前端永远不碰支付核心参数,它只拿到一张“预支付小票”;真正跟收银机对话的是后端,这就是支付集成最基础的架构纪律。
  1. 参数集 params统一下单必须携带 appid、mch_id、out_trade_no、total_fee、notify_url、nonce_str 等业务参数。
    params={appid, mch_id, out_trade_no, total_fee, … }\text{params} = \{\text{appid},\ \text{mch\_id},\ \text{out\_trade\_no},\ \text{total\_fee},\ \dots\}
  2. 附加签名 sign对 params 按规则生成签名后一并提交,收银机验签通过才认为订单内容没被篡改。
    params∪{sign}\text{params} \cup \{\text{sign}\}
  3. 返回 prepay_id微信支付受理后返回预支付单号,它是后续 wx.requestPayment 的核心参数。
    prepay_id\text{prepay\_id}

签名生成机制

每一张小票上都要盖一个防伪章,章的内容不是随意刻的,而是把菜品名、桌号、金额按固定顺序排好,滴上秘制酱汁(API 密钥),再丢进烤箱(MD5 算法)烤出一串字符。微信支付的签名,就是这枚“内容可校验”的防伪章。

签名规则并不复杂:第一步,把所有参数按参数名 ASCII 码升序排序;第二步,拼成 key1=value1&key2=value2 的形式;第三步,在末尾拼接 &key=商户API密钥;第四步,对整个字符串做 MD5 哈希,再转大写。任何一方拿到参数,只要用同一套规则重新算一遍,就能知道消息是不是被掉包过。

防伪章的妙处在于:哪怕有人偷偷改了小票上的金额,章就对不上了。收银台一眼就能识破——签名不是为了让章好看,而是让任何篡改都留下破绽。

sign=MD5(sort_join(params)+api_key)\text{sign} = \text{MD5}\left(\text{sort\_join}(\text{params}) + \text{api\_key}\right)
验签的本质是“对暗号”:你和收银台共享一把密钥,谁拿着正确的密钥算出同样的章,谁就是可信的。
  1. 参数名 ASCII 升序用字典序把参数排列整齐,保证双方按统一顺序拼接。
    sort(keys)\text{sort}(\text{keys})
  2. 拼接 key=value 串每个参数变成 k=v,用 & 连接,相当于把菜品清单按固定格式列好。
    \text{k\_1=v\_1\&k\_2=v\_2\&\cdots}
  3. 追加 api_key末尾补上商户密钥,这是只有你和微信支付知道的“酱汁配方”。
    raw=sort_join(params)+api_key\text{raw} = \text{sort\_join}(\text{params}) + \text{api\_key}
  4. MD5 并转大写对 raw 做摘要,输出 32 位大写十六进制串作为 sign。
    sign=MD5(raw)→大写 hex\text{sign} = \text{MD5}(\text{raw}) \to \text{大写 hex}

回调验签处理

顾客扫码付款后,钱并没有直接跑进你的口袋,而是由收银台(微信支付服务器)发来一张“结账确认单”。老板收到确认单,第一步不是欢呼,而是验章、对钱、核对桌号——全部通过,才在回执上签"SUCCESS";如果先发货再对账,碰上假确认单就亏大了。

回调验签是支付闭环的临门一脚。微信支付把订单结果 POST 到下单时填的 notify_url,回调内容是一段 XML 或 JSON,其中带 sign 字段。后端收到后先验签:把 sign 字段剔除,对剩余参数重新排序拼接计算 MD5,与回调里的 sign 比对;验签通过后还不能急着处理业务,必须继续比对 total_fee 与数据库订单金额、out_trade_no 与自己的商户单号、订单状态是否为未支付。全部通过后更新订单状态,并应答 SUCCESS 字符串通知微信停止重试。

这一章像极了财务的复核习惯:单据上章是真的,不意味着钱就能入账,账实相符才是收款的唯一依据。信任是分层的——防伪章解决“谁发的”,业务校验解决“该不该收”。

sign′=?sign,sign′=MD5(sort_join(notify_data)+api_key)\text{sign}' \stackrel{?}{=} \text{sign},\quad \text{sign}' = \text{MD5}(\text{sort\_join}(\text{notify\_data}) + \text{api\_key})
验签只证明“这封信确实是微信支付写的”,要证明“这笔钱的金额和订单一致”,还得靠业务侧的金额与状态校验。
  1. 剔除回调中的 sign先取出 sign 字段,剩下的参数才是参与计算的数据。
    notify_data=params∖{sign}\text{notify\_data} = \text{params} \setminus \{\text{sign}\}
  2. 重新计算签名按签名生成规则重新得到 sign'。
    sign′=MD5(sort_join(notify_data)+api_key)\text{sign}' = \text{MD5}(\text{sort\_join}(\text{notify\_data}) + \text{api\_key})
  3. 比对与业务核验sign' 与 sign 相等,且金额、单号、状态都对,才应答 SUCCESS。
    sign′=sign∧amount_ok∧order_ok⇒SUCCESS\text{sign}' = \text{sign} \wedge \text{amount\_ok} \wedge \text{order\_ok} \Rightarrow \text{SUCCESS}

登录态维护

顾客第一次来,你查了总台的档案,但总台不可能每次都帮你核身份。于是老板决定:自己给老顾客发一张会员卡。这张卡就是登录态 token:后端用 openid 和过期时间生成一个带签名的凭证,前端存进本地缓存,以后每次请求都带上它,后端凭卡认人。

业界最常见的方案是自定义登录态:登录成功后,后端把 openid、session_key 落到 Redis,并生成一个随机字符串 token(也叫 3rd_session)返回前端;前端通过 wx.setStorageSync 保存。后续请求在 header 里带 token,后端查 Redis 校验有效性并续期。token 应设置有效期,过期后让小程序重新调 wx.login 走静默登录刷新,而不是要求用户每次都重新授权。

会员卡比取号牌聪明得多:它记住了你是谁,而不是每次从头核身份。但卡片必须写清到期时间,否则一张永不失效的会员卡,丢了就是一辈子的安全隐患。

token=HMAC_SHA256(uid∥expire, secret)\text{token} = \text{HMAC\_SHA256}\left(\text{uid} \| \text{expire},\ \text{secret}\right)
token 是老板自制的会员卡:里面写着顾客 ID 和有效期,并盖上只有老板知道的签名,顾客拿卡即身份。
  1. uid 与 expireuid 是 openid 对应的用户主键,expire 是过期时间戳,两者拼接成 card 明文。
    uid∥expire\text{uid} \| \text{expire}
  2. HMAC_SHA256 签名用服务端 secret 对明文签名,防止顾客私自篡改卡上的有效期。
    HMAC_SHA256(payload, secret)\text{HMAC\_SHA256}(\text{payload},\ \text{secret})
  3. token 签发与校验签名结果作为 token 下发;后端每次请求重新验签、检查 expire,过期即拒绝。
    verify(token)→{uid, expire}\text{verify}(\text{token}) \to \{\text{uid},\ \text{expire}\}

支付安全防控

老板的餐厅越开越大,财务规矩也越来越严:同一张小票绝不结算两次,金额必须和菜单完全一致,深夜连续来三个大订单要人工复核。支付安全防控,正是把这些“餐厅老规矩”翻译成代码。

第一道防线是幂等防重:out_trade_no 在商户体系内必须唯一,处理回调前先查订单状态,已支付的单子直接忽略;第二道防线是金额校验:永远以数据库中的订单金额为准,不能轻信前端传参;第三道防线是防重放:请求里带 nonce_str 和时间戳,后端对同一 nonce 去重、对过期时间戳拒绝;第四道防线是风控:对异常 IP、频繁下单、设备指纹突变进行识别和拦截。

老板的规矩看起来繁琐,关键时刻却能救命。支付安全防控的精髓,就是把人类财务室里最朴素的谨慎,变成机器毫秒级的自动化判断。

paid_amount=?order_amount∧order_status=UNPAID⟶PAID\text{paid\_amount} \stackrel{?}{=} \text{order\_amount} \quad\wedge\quad \text{order\_status} = \text{UNPAID} \longrightarrow \text{PAID}
支付安全不是某一个接口的魔法,而是一串环环相扣的“确认文化”:重复的不收、金额不对的不收、来路可疑的不收。
  1. 金额比对回调中的 total_fee 必须与数据库订单金额相等,防止伪造低价或高价单。
    paid_amount=order_amount\text{paid\_amount} = \text{order\_amount}
  2. 状态机判断订单必须处于 UNPAID 才能流转为 PAID,已支付订单再次通知直接忽略。
    status:UNPAID→PAID\text{status}: \text{UNPAID} \to \text{PAID}
  3. 防重放与风控nonce_str 去重、时间戳窗口校验、风控策略集共同构成外围防线。
    nonce_fresh∧timestamp_near∧risk_pass\text{nonce\_fresh} \wedge \text{timestamp\_near} \wedge \text{risk\_pass}

AI 应用场景

智能风控识别异常登录与盗刷

阶段:数据采集 + 模型推理

解决的问题:传统登录支付只靠规则匹配,面对撞库、盗刷时响应迟缓;AI 可以对登录时间、IP 归属地、设备指纹、下单频率统一建模,实时给每笔请求打风险分。

具体案例:某电商小程序在 token 校验基础上,将 openid、IP、设备型号、下单间隔输入梯度提升树模型,识别出“凌晨异地 + 陌生设备 + 快速下单”的盗刷团伙,拦截率提升 60%。

AI 客服结合登录态查询订单

阶段:应用集成

解决的问题:用户在小程序咨询订单时,反复验证身份体验很差;利用登录态 token 对应用户与订单库,AI 客服可以直接读取“这位顾客”的历史订单上下文。

具体案例:餐饮小程序接入大模型客服后,用户发送“我昨天那单退了吗”,后端凭 token 定位 openid,把订单状态注入提示词,客服机器人给出准确答复,转人工率降低 40%。

用户画像驱动的个性化推荐

阶段:特征工程

解决的问题:登录授权获得的基础资料与支付行为分散在不同库,难以形成统一画像;登录支付打通后,以 openid 作为主键融合多源数据。

具体案例:用户在授权后,后端解密得到 unionid,将小程序内的浏览、下单、退单行为归到同一用户 ID 下,用协同过滤推荐菜品,点击率提升 25%。

Python 演示

python72 行
import hashlibimport hmacimport json APP_ID = "wx123456"APP_SECRET = "backend_secret"API_KEY = "merchant_key_888"  def code2session(code, appid, secret):    """模拟微信登录服务: code 换 session_key 和 openid"""    if code.startswith("tmp_") and appid == APP_ID and secret == APP_SECRET:        return {"openid": "oX8_demo_001", "session_key": "sk_demo_9527"}    return None  def make_sign(params, api_key):    """微信支付 v2 风格签名: ASCII 升序 + 拼接 + MD5 大写"""    keys = sorted(params.keys())    raw = "&".join("{}={}".format(k, params[k]) for k in keys)    raw += "&key=" + api_key    return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()  def decrypt_user_info(encrypted, session_key):    """简化版解密, 真实项目用 AES-128-CBC"""    sk = session_key.encode("utf-8")    raw = bytes([b ^ sk[i % len(sk)] for i, b in enumerate(encrypted)])    return json.loads(raw.decode("utf-8"))  def main():    # 阶段一: 登录换取会话    code = "tmp_login_code_001"    sess = code2session(code, APP_ID, APP_SECRET)    if sess is None:        raise RuntimeError("code 无效或已过期")    openid = sess["openid"]    token = hmac.new(APP_SECRET.encode(), openid.encode(), hashlib.sha256).hexdigest()    print("登录成功 openid:", openid, "token:", token[:12], "...")     # 阶段二: 解密用户信息    raw_data = b'{"nickName": "demo"}'    sk = sess["session_key"].encode("utf-8")    encrypted = bytes([c ^ sk[i % len(sk)] for i, c in enumerate(raw_data)])    user = decrypt_user_info(encrypted, sess["session_key"])    print("用户信息:", user)     # 阶段三: 支付下单与回调验签    order = {        "appid": APP_ID,        "mch_id": "1900000109",        "out_trade_no": "20240607001",        "total_fee": 1,        "notify_url": "https://api.example.com/pay/callback",        "body": "demo-order",    }    order["sign"] = make_sign(order, API_KEY)    print("下单签名:", order["sign"])     notify = dict(order)    notify["trade_state"] = "SUCCESS"    del notify["sign"]    notify["sign"] = make_sign(notify, API_KEY)    received = dict(notify)    received_sign = received.pop("sign")    if make_sign(received, API_KEY) == received_sign:        print("回调验签通过")  if __name__ == "__main__":    main()
目的:用 Python 完整演示小程序登录换会话、解密用户信息、支付下单签名与回调验签的最小闭环,可直接运行观察输出。
  1. def code2session(code, appid, secret):模拟微信登录服务,用一次性 code 换回 session_key 和 openid,相当于老板拿号牌去总台核档。
  2. raw = "&".join("{}={}".format(k, params[k]) for k in keys)按排序后的参数生成待签名字符串,这是签名算法最核心的拼接逻辑。
  3. return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()用 MD5 生成 32 位大写签名,微信支付 v2 的标志性操作。
  4. token = hmac.new(APP_SECRET.encode(), openid.encode(), hashlib.sha256).hexdigest()用 HMAC-SHA256 给 openid 加签,生成自定义登录态 token。
  5. encrypted = bytes([c ^ sk[i % len(sk)] for i, c in enumerate(raw_data)])模拟微信返回的加密数据,真实项目由 wx.getUserProfile 返回 encryptedData。
  6. del notify["sign"]计算回调签名前必须剔除上一次的 sign,避免把旧签名混进新签名计算。

常见误区

误区“wx.login 拿到的 code 就是登录态,我在前端把它存起来,下次直接用,省得每次都要重新登录。”

正解code 是临时取号牌,只能换一次,5 分钟后作废。餐厅不会让顾客第二天还拿同一张票入座。正确做法是后端用 code 换 session_key,并建立自己的登录态 token,前端保存的是 token 而不是 code。

为何常见:code 以普通字符串形式返回,看起来很像可以复用的“令牌”,而文档中的过期时间容易被忽略。

误区“session_key 是后端换回来的,但前端要用它解密用户信息,所以应该把它返回给小程序使用。”

正解session_key 是敏感密钥,只能保存在后端。需要解密时,把 encryptedData 和 iv 发给后端,由后端解密后返回结果。就像老板的密码本不能交给顾客,只能老板自己核对会员信息。

为何常见:看到“解密必须用 session_key”就以为前端也要持有它,忽略了它和密码同等的敏感级别。

误区“支付回调都验签通过了,说明钱一定到账,直接发货、改订单状态吧。”

正解验签只证明消息来自微信支付,不代表这笔订单一定成功。必须再比对金额、商户单号、订单状态,确认零篡改、无重复通知。确认单上有老板的章还不够,还要看菜品和金额对不对。

为何常见:验签通过带来的信任感,让人忽略了业务层校验;攻击者常利用这种心理伪造相似通知。

误区“token 是登录态,只要签发就好了,永久有效用户更方便,不用反复登录。”

正解token 是打开会话大门的钥匙,永久有效的钥匙一旦被盗就永久沦陷。要设置过期时间、刷新机制和风险失效策略。餐厅的会员卡也会定期要求验证身份,防止别人捡到你的卡一直用。

为何常见:追求用户体验而牺牲安全,是最常见的登录态设计陷阱,尤其在“记住我”这类需求面前。

面试题

Q1. wx.login 获取的 code 有效期大约是多久?

单选题 · easy

  • A.约 5 分钟← 正确答案
  • B.约 2 小时
  • C.约 1 天
  • D.约 7 天

答案:A。code 是临时取号牌,设计上就是为了短命,约 5 分钟过期且只能用一次。2 小时、1 天、7 天都太长,不符合“降低泄露风险”的设计初衷。

Q2. code2Session 接口返回的两个核心字段是?

单选题 · easy

  • A.openid 和 session_key← 正确答案
  • B.access_token 和 openid
  • C.token 和 secret_key
  • D.unionid 和 token

答案:A。code2Session 返回的是用户唯一标识 openid 和会话密钥 session_key。access_token 是公众号/开放平台的概念,token 和 secret_key 是自定义登录态的内容,不属于该接口。

Q3. session_key 应该存储在哪里?

单选题 · mid

  • A.后端存储,不下发前端← 正确答案
  • B.存在小程序本地缓存中
  • C.随登录态下发给客户端
  • D.保存在微信支付商户号中

答案:A。session_key 是密码本,只能留在后端。下发给前端或存本地缓存,等于把密码本交给顾客,一旦被破解用户数据就会泄露。它与商户号无关。

Q4. 微信支付统一下单接口的核心返回值是?

单选题 · mid

  • A.prepay_id 预支付单号← 正确答案
  • B.transaction_id 交易号
  • C.out_trade_no 商户单号
  • D.session_id 会话号

答案:A。统一下单成功后返回 prepay_id,它是调起小程序收银台(wx.requestPayment)的关键参数。transaction_id 是支付成功后的微信交易号,out_trade_no 是自己生成的商户单号。

Q5. 微信支付 v2 签名时,参数排列规则是?

单选题 · mid

  • A.按参数名 ASCII 码升序← 正确答案
  • B.按参数值从大到小排列
  • C.按参数加入顺序原样排列
  • D.按参数名长度从短到长排列

答案:A。签名第一步是把所有参数名按 ASCII 码升序排序,这是双方能算出相同签名的前提。按值排序、按加入顺序或长度排序都会导致两端结果不一致。

Q6. 支付回调验签通过后,后端首先应该做什么?

单选题 · hard

  • A.核对订单金额与状态← 正确答案
  • B.立即给用户发货完成
  • C.把回调消息转发前端
  • D.标记风险订单等人工

答案:A。验签只证明“信是微信支付的”,还要核对金额、商户单号和订单状态,确认账实相符才更新订单并应答 SUCCESS。这相当于对完章还要对钱,不能直接发货。

Q7. 下列哪种做法能有效防止支付回调重放攻击?

单选题 · hard

  • A.校验 out_trade_no 是否已处理← 正确答案
  • B.每次回调都发起新支付请求
  • C.用前端传入金额更新订单
  • D.将回调日志直接写入客户端

答案:A。用 out_trade_no 做幂等控制,同一笔订单的重复回调会被状态机拦下。每次发起新支付请求会制造新订单,用前端金额更新订单等于把定价权交给攻击者,都不安全。

Q8. 关于小程序登录流程,以下说法正确的有?

多选题 · mid

  • A.wx.login 得到一次性 code← 正确答案
  • B.code 可重复换取 session_key
  • C.session_key 必须下发前端解密
  • D.openid 是唯一用户标识← 正确答案

答案:AD。wx.login 得到的一次性 code 只能用一次,openid 是用户在小程序下的唯一标识。code 不能重复换 session_key,session_key 也不能下发给前端,所以 B、C 错误。

Q9. 支付回调处理中,必须完成的校验有哪些?

多选题 · hard

  • A.验签确认来自微信支付← 正确答案
  • B.比对金额与订单金额一致← 正确答案
  • C.用 out_trade_no 做幂等控制← 正确答案
  • D.拒收所有金额小于 1 分的单

答案:ABC。验签、金额比对、幂等控制是回调处理的三道必答题。金额小于 1 分的单不一定非法,比如 0 元体验单,不能一刀切拒收,所以 D 错误。

Q10. 以下哪些措施能提升登录支付安全性?

多选题 · mid

  • A.为自定义 token 设置过期时间← 正确答案
  • B.把 API 密钥写进小程序代码
  • C.使用 nonce_str 防止重放攻击← 正确答案
  • D.让前端直接传 total_fee 给支付

答案:AC。token 过期能限制被盗用窗口,nonce_str 配合时间戳能防重放。API 密钥写进前端等于把保险柜钥匙贴门口,total_fee 必须由后端按业务计算,均不可取。