![](/img/trans.png)
[英]Difference between Expected digest and Actual digest in CXF web service (Java)
[英]How to create Password Digest for use with Web Service?
我正在尝试创建一个 passwordDigest 实用程序,它可以在能够运行 Java 字节码的不同环境中使用。
首先,我创建了 nonce。 它是这样完成的。
public static String buildNonce(){
StringBuffer nonce=new StringBuffer();
String dateTimeString = Long.toString(new Date().getTime());
byte[] nonceByte= dateTimeString.getBytes();
return Base64.encode(nonceByte);
}
一旦我有了随机数,我就会构建密码摘要。
public static String buildPasswordDigest(String userName, String password, String nonce, String dateTime){
MessageDigest sha1;
String passwordDigest=null;
try {
sha1= MessageDigest.getInstance("SHA-1");
byte[] hash = MessageDigest.getInstance("SHA-1").digest(password.getBytes("UTF-8"));
sha1.update(nonce.getBytes("UTF-8"));
sha1.update(dateTime.getBytes("UTF-8"));
passwordDigest = new String(Base64.encode(sha1.digest(hash)));
sha1.reset();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return passwordDigest;
为了测试一切正常。 我已经使用 CXF 2.7 创建了一个测试 Web 服务。 我手动创建了 SOAP Envelope 来测试身份验证。 信封长这个样子。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.mytest.org/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance/">
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-2">
<wsse:Username>TEST_USER</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">UZsDSW/vANu6fHg4rAHo2OwsF9s=</wsse:Password
<wsse:Nonce>MTQwMTMwMDQzNjA3OA==</wsse:Nonce>
<wsu:Created>2014-05-28T18:07:16.087Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<ws:record>
<val1>1</val1>
<val2>Some Text</val2>
</ws:record>
</soapenv:Body>
</soapenv:Envelope>
当我使用 SOAP UI 发送信封时。 我收到以下身份验证错误。
WARNING: Interceptor for {http://ws.mytest.org/}TestService has thrown exception, unwinding now
org.apache.cxf.binding.soap.SoapFault: The security token could not be authenticated or authorized
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.createSoapFault(WSS4JInInterceptor.java:788)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:336)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:95)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:239)
at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:248)
at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:222)
at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:153)
at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:167)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:286)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:206)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.ws.security.WSSecurityException: The security token could not be authenticated or authorized
at org.apache.ws.security.validate.UsernameTokenValidator.verifyDigestPassword(UsernameTokenValidator.java:199)
at org.apache.ws.security.validate.UsernameTokenValidator.validate(UsernameTokenValidator.java:97)
at org.apache.ws.security.processor.UsernameTokenProcessor.handleUsernameToken(UsernameTokenProcessor.java:172)
at org.apache.ws.security.processor.UsernameTokenProcessor.handleToken(UsernameTokenProcessor.java:67)
at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:396)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:279)
... 31 more
我怀疑我在创建随机数或密码时遇到问题。
感谢您的帮助。
WS-Security 将密码摘要定义为
Base64(SHA1(随机数+创建+密码))
不是
Base64(SHA1(密码+随机数+创建))
并且 nonce 应该是 128 位(16 字节)编码为 Base64。 例如
java.security.SecureRandom random = java.security.SecureRandom.getInstance("SHA1PRNG");
random.setSeed(System.currentTimeMillis());
byte[] nonceBytes = new byte[16];
random.nextBytes(nonceBytes);
String nonce = new String(org.apache.commons.codec.binary.Base64.encodeBase64(nonceBytes), "UTF-8");
应该构造密码的随机数和摘要,就像在下面的代码段中所做的那样。
请注意元素的顺序,以及nonce
的非编码版本用于passwordDigest
构造的事实。
import javax.xml.datatype.*;
import java.security.*;
import java.time.Instant;
import java.util.Base64;
import static java.lang.System.currentTimeMillis;
import static java.nio.charset.StandardCharsets.UTF_8;
class Snippet {
private static final SecureRandom RANDOM;
private static final int NONCE_SIZE_IN_BYTES = 16;
private static final String MESSAGE_DIGEST_ALGORITHM_NAME_SHA_1 = "SHA-1";
private static final String SECURE_RANDOM_ALGORITHM_SHA_1_PRNG = "SHA1PRNG";
static {
try {
RANDOM = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM_SHA_1_PRNG);
RANDOM.setSeed(currentTimeMillis());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws DatatypeConfigurationException {
final var nonceBytes = generateNonce();
final var password = "password";
final var createdDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(Instant.now().toString());
final var passwordDigestBytes = constructPasswordDigest(nonceBytes, createdDate, password);
final var base64Encoder = Base64.getEncoder();
final var nonceBase64Encoded = base64Encoder.encodeToString(nonceBytes);
final var passwordDigestBase64Encoded = base64Encoder.encodeToString(passwordDigestBytes);
System.out.println(String.format("nonce: [%s], password digest: [%s]", nonceBase64Encoded, passwordDigestBase64Encoded));
System.out.flush();
}
private static byte[] generateNonce() {
var nonceBytes = new byte[NONCE_SIZE_IN_BYTES];
RANDOM.nextBytes(nonceBytes);
return nonceBytes;
}
/**
* @noinspection SameParameterValue
*/
private static byte[] constructPasswordDigest(byte[] nonceBytes, XMLGregorianCalendar createdDate, String password) {
try {
final var sha1MessageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_NAME_SHA_1);
sha1MessageDigest.update(nonceBytes);
final var createdDateAsString = createdDate.toString();
sha1MessageDigest.update(createdDateAsString.getBytes(UTF_8));
sha1MessageDigest.update(password.getBytes(UTF_8));
return sha1MessageDigest.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
此代码段的输出是:
随机数:[KEYJbsmxL1JdsDHo7kWD6Q==],密码摘要:[+OiJDs2sEycZECHhQdJ8T9Lt2ns=]
经过几天的搜索,Pavel 的答案是唯一对我有用的答案。 我对他的代码所做的唯一更改是 1) 返回令牌创建日期,以及随机数和密码摘要。 这对于避免“过期令牌”响应至关重要。 2) 返回整个头块 3) 将代码转换为 Java 1.8。
这是修改后的 Pavel 代码。
package test;
import javax.xml.datatype.*;
import java.security.*;
import java.time.Instant;
import java.util.Base64;
import java.util.Base64.Encoder;
import static java.lang.System.currentTimeMillis;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Main {
private static final SecureRandom RANDOM;
private static final int NONCE_SIZE_IN_BYTES = 16;
private static final String MESSAGE_DIGEST_ALGORITHM_NAME_SHA_1 = "SHA-1";
private static final String SECURE_RANDOM_ALGORITHM_SHA_1_PRNG = "SHA1PRNG";
static {
try {
RANDOM = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM_SHA_1_PRNG);
RANDOM.setSeed(currentTimeMillis());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws DatatypeConfigurationException {
String passwordMd5 = "4C2d85FBEE80145B4";
generateHeader(passwordMd5);
}
public static void generateHeader(String password) throws DatatypeConfigurationException {
final byte[] nonceBytes = generateNonce();
final XMLGregorianCalendar createdDate = DatatypeFactory.newInstance()
.newXMLGregorianCalendar(Instant.now().toString());
final byte[] passwordDigestBytes = constructPasswordDigest(nonceBytes, createdDate, password);
final Encoder base64Encoder = Base64.getEncoder();
final String nonceBase64Encoded = base64Encoder.encodeToString(nonceBytes);
final String passwordDigestBase64Encoded = base64Encoder.encodeToString(passwordDigestBytes);
//System.out.println(String.format("nonce: [%s], password digest: [%s]", nonceBase64Encoded, passwordDigestBase64Encoded));
StringBuilder sb = new StringBuilder();
sb.append("<soapenv:Header>\n");
sb.append("<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n");
sb.append("<wsse:UsernameToken>");
sb.append("<wsse:Username>thisIsMyUsername</wsse:Username>");
sb.append("<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">"+
passwordDigestBase64Encoded+"</wsse:Password>\n");
sb.append("<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004 /01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
nonceBase64Encoded+"</wsse:Nonce>\n");
sb.append("<wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
createdDate.toString()+"</wsu:Created>\n");
sb.append("</wsse:UsernameToken>\n");
sb.append("</wsse:Security>\n");
sb.append("</soapenv:Header>");
System.out.println(sb.toString());
System.out.flush();
System.exit(0);
}
private static byte[] generateNonce() {
byte[] nonceBytes = new byte[NONCE_SIZE_IN_BYTES];
RANDOM.nextBytes(nonceBytes);
return nonceBytes;
}
private static byte[] constructPasswordDigest(byte[] nonceBytes, XMLGregorianCalendar createdDate,
String password) {
try {
final MessageDigest sha1MessageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_NAME_SHA_1);
sha1MessageDigest.update(nonceBytes);
final String createdDateAsString = createdDate.toString();
sha1MessageDigest.update(createdDateAsString.getBytes(UTF_8));
sha1MessageDigest.update(password.getBytes(UTF_8));
return sha1MessageDigest.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.