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

Java ONVIF GSoap授权请求HTTP 401错误排查求助

排查ONVIF摄像头HTTP 401未授权问题:UsernameTokenProfile实现错误分析

嘿,我帮你梳理下代码里的几个关键问题,这些应该就是导致401未授权的核心原因,尤其是你怀疑的Nonce确实有不小的问题:

1. Nonce生成完全不符合规范

你现在的Nonce是基于时间戳生成的十六进制字符串,这违反了OASIS UsernameTokenProfile的明确要求:

  • Nonce必须是随机的字节序列(每个请求都要唯一,绝对不能重复),不能依赖时间戳(哪怕时间戳是唯一的,规范也强制要求用随机值)
  • 你当前的生成方式会导致同一毫秒内的请求Nonce重复,而且处理逻辑错误:应该先生成随机字节,再做Base64编码,不是先转十六进制字符串再截取长度。

2. PasswordDigest计算逻辑错误

规范里的PasswordDigest计算公式是:
PasswordDigest = Base64( SHA-1( 原始Nonce字节 + Created时间的UTF-8字节 + 原始密码的UTF-8字节 ) )
你的代码犯了两个致命错误:

  • 用了字符串拼接(nonce字符串 + now字符串 + pass字符串)再转字节,而不是直接拼接原始字节
  • 你用的nonce是十六进制字符串的字节,而不是生成的随机原始字节

3. XML中同时存在两种Password类型

你的XML里同时包含了PasswordText和PasswordDigest元素,这是绝对不允许的!根据规范,一个UsernameToken只能选择一种认证方式:要么明文密码(PasswordText),要么摘要(PasswordDigest)。ONVIF设备几乎都要求用PasswordDigest方式,所以必须删掉PasswordText那一行。

4. 时间处理不够严谨

你手动减去3小时来对齐UTC,这种方式容易出错(比如夏令时变化)。正确的做法是直接让日期格式化器输出UTC时间,不用手动计算偏移。


修正后的代码示例

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.Base64;

public class OnvifAuth {
    private static final String login = "your_login";
    private static final String pass = "your_password";
    // 直接设置UTC时区的格式化器,避免手动计算偏移
    private static final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    static {
        dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    public static String getAuthXML(String body) throws Exception {
        // 1. 生成符合规范的随机Nonce(16字节随机数)
        SecureRandom random = new SecureRandom();
        byte[] nonceBytes = new byte[16];
        random.nextBytes(nonceBytes);
        String nonceBase64 = Base64.getEncoder().encodeToString(nonceBytes);

        // 2. 生成UTC标准格式的Created字段
        String created = dateTimeFormat.format(new Date());

        // 3. 严格按规范计算PasswordDigest:字节级拼接后SHA-1,再Base64编码
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(nonceBytes);
        md.update(created.getBytes("UTF-8"));
        md.update(pass.getBytes("UTF-8"));
        byte[] digestBytes = md.digest();
        String passwordDigest = Base64.getEncoder().encodeToString(digestBytes);

        // 4. 构建正确的SOAP信封(仅保留PasswordDigest类型)
        return "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">"
                + "<s:Header>"
                + "<Security s:mustUnderstand=\"1\" xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"
                + "<UsernameToken>"
                + "<Username>" + login + "</Username>"
                + "<Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"
                + passwordDigest + "</Password>"
                + "<Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">"
                + nonceBase64 + "</Nonce>"
                + "<Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"
                + created + "</Created>"
                + "</UsernameToken>"
                + "</Security>"
                + "</s:Header>"
                + "<s:Body xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
                + body
                + "</s:Body>"
                + "</s:Envelope>";
    }
}

这些修正应该能解决你的401问题,核心是严格遵循规范里的字节级计算和元素要求,不要再混合密码类型,同时确保Nonce是真正的随机值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:10:27