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

React Native中Digest认证失败,Postman可正常运行求排查

React Native Digest认证401失败排查问题

问题描述

我在React Native中尝试实现Digest认证多日未果,但相同参数在Postman中可正常运行,怀疑问题出在nonce上。我已尝试通过WWW-Authenticate头获取nonce和realm,但仍收到401认证失败响应,以下是服务器响应信息及最新代码,请求排查问题原因。

服务器响应

Authentication failed {"type":"default","status":401,"ok":false,"statusText":"","headers":{"map":{"connection":"keep-alive", "content-length":"331","content-type":"application/json","date":"Wed, 08 Nov 2023 16:57:28 GMT","keep-alive":"timeout=5 , max=99","x-content-type-options":"nosniff","x-frame-options":"SAMEORIGIN","x-xss-protection":"1; mode=block"}}, "url":"http://192.168.18.209/ISAPI/AccessControl/UserInfo/Record?format=json","bodyUsed":false,"_bodyInit":{"_data":{"size":331,"offset ":0,"blobId":"5584F5A4-1C07-4299-B89E-95202A6B39EA","type":"application/json","name":"Record.json","__collector":{}}}," _bodyBlob":{"_data":{"size":331,"offset":0,"blobId":"5584F5A4-1C07-4299-B89E-95202A6B39EA","type":"application/json","name" :"Record.json","__collector":{}}}}

"Digest username=\"admin\", realm=\"DS-526ECD9D\", nonce=\"MDcyMjY1YjA0ZjNjY2ZlNzkzZTQzODM5ODYzMTA4NzI=\", uri=\"http://192.168.18.209/ISAPI/AccessControl/UserInfo /Record?format=json\", algorithm=\"MD5\", qop=\"auth\", nc=\"0000001\", cnonce=\"0000001\", response=\"b88b651b10927a0b99ba26eb2cb22eba\""

最新React Native代码

import React, { useState } from 'react';
import { View, TextInput, Button } from 'react-native';
import md5 from 'md5';

export default function Sync() {
  const password = 'pass'; // 密码
  const username = 'admin'; // 用户名
  const algorithm = 'MD5'; // 算法

  const [url, setUrl] = useState('http://192.168.18.209/ISAPI/AccessControl/UserInfo/Record?format=json');

  const handleRequest = async () => {
    try {
      const initialResponse = await fetch(url, {
        method: 'GET',
      });

      if (initialResponse.status === 401) {
        const wwwAuthenticateHeader = initialResponse.headers.get('WWW-Authenticate');

        if (!wwwAuthenticateHeader) {
          console.log('认证失败 - 未找到WWW-Authenticate头');
          return;
        }

        const nonceHeaderValue = initialResponse.headers.get('www-authenticate');
        const matchNonce = nonceHeaderValue && nonceHeaderValue.match(/nonce="([^"]+)"/);
        const nonce = matchNonce && matchNonce[1];

        const matchRealm = nonceHeaderValue && nonceHeaderValue.match(/realm="([^"]+)"/);
        const realm = matchRealm && matchRealm[1];

        const qop = 'auth';
        console.log('qop:', qop);

        console.log('Nonce:', nonce);
        console.log('Realm:', realm);

        const authHeader = generateDigestAuthHeader(username, password, algorithm, wwwAuthenticateHeader);

        const response = await fetch(url, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': authHeader,
          },
          body: JSON.stringify({
            UserInfo: {
              employeeNo: "118",
              name: "Antonella Dantas",
              // ...
            },
          }),
        });

        if (response.status === 200) {
          console.log('认证成功');
        } else {
          console.log('认证失败'+JSON.stringify(response));
        }
      } else {
        console.log('认证失败 - 初始请求未返回401状态');
      }
    } catch (error) {
      console.error('GET请求错误:', error);
    }
  };

  function generateDigestAuthHeader(username, password, algorithm, wwwAuthenticateHeader) {

    const nonceHeaderValue = wwwAuthenticateHeader;
    
    const matchNonce = nonceHeaderValue && nonceHeaderValue.match(/nonce="([^"]+)"/);
    const nonce = matchNonce ? matchNonce[1] : null;

    const matchRealm = nonceHeaderValue && nonceHeaderValue.match(/realm="([^"]+)"/);
    const realm = matchRealm ? matchRealm[1] : null;

    const qop = 'auth';

    if (nonce === null || realm === null) {
      console.error('在www-authenticate头中未找到Nonce或Realm');
      return null; // 按需处理错误
    }

    console.log('Nonce2:', nonce);
    console.log('Realm2:', realm);
    console.log('Qop2:', qop);

    const nc = '0000001';
    const cnonce = '0000001';
    const method = 'POST';

    const A1 = md5(username + ':' + realm + ':' + password);
    const A2 = md5(method + ':' + url);
    const response = md5(A1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + md5(A2));

    return `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${url}", algorithm="${algorithm}", qop="${qop}", nc="${nc}", cnonce="${cnonce}", response="${response}"`;
  }

  return (
    <View>
      <TextInput
        placeholder="URL"
        value={url}
        onChangeText={setUrl}
      />
      <Button title="发起请求" onPress={handleRequest} />
    </View>
  );
}

可能的问题点及修复建议

  • URI参数不一致:服务器返回的认证头中,uri字段包含空格:"http://192.168.18.209/ISAPI/AccessControl/UserInfo /Record?format=json",而代码中使用的url无空格,这会导致response计算错误。需确保生成认证头时的uri与服务器返回的完全一致,或者检查服务器是否返回了错误的uri,必要时手动修正空格问题。
  • 请求方法不匹配:初始请求用GET获取nonce,但后续认证请求用POST,Digest认证中A2的计算依赖请求方法。需确保计算A2时使用的method与实际请求方法一致,或者在POST请求前重新发起一次POST请求获取最新的nonce(部分服务器会针对不同请求方法返回不同nonce)。
  • nonce重复使用:部分服务器要求nonce只能单次使用,初始GET请求获取的nonce可能已失效。建议在发起POST认证请求前,先发起一次不带认证头的POST请求,获取最新的nonce后再计算认证头。
  • cnonce固定值问题:固定使用0000001作为cnonce可能不符合服务器要求,建议生成随机字符串作为cnonce(例如:md5(Math.random().toString(36).substring(2)))。
  • nc计数未递增:nc是请求计数,每次使用同一个nonce时需要递增数值。固定为0000001可能导致认证失败,需维护一个计数器,每次请求时递增并格式化为8位十六进制字符串。

内容的提问来源于stack exchange,提问作者Bonfim Jr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:07:02