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
相关产品推荐
相关产品推荐

