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

