What's different two `signWith()` methods? - jjwt

signWith(SignatureAlgorithm alg, Key key) has deprecated. We should use signWith(Key, SignatureAlgorithm) instead. But we how to do it. just swap the position?
How should I change the original code as follows to use a correct method?
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;
public static final String JWT_KEY = "JSDFSDFSDFASJDHASDASDdfa32dJHASFDA67765asda123dsdsw";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}
I notice that its doc for key is different.
The deprecated is key – the algorithm-specific signing key to use to digitally sign the JWT.
The other is key – the signing key to use to digitally sign the JWT.
So I think the key is different. But I don't know how to adjust my code.

Since signWith(SignatureAlgorithm, SecretKey) is deprecated, you can use signWith(SecretKey) or signWith(SecretKey, SignatureAlgorithm).
When using HMAC-SHA, ensure that the secret key provided is at least as many bits as the algorithm's signature.
HMAC-SHA-256: 256 bits.
HMAC-SHA-384: 384 bits.
HMAC-SHA-512: 512 bits.
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
.
.
.
SecretKey secretKey = generalKey();
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(secretKey) //The signature algorithm is selected according to the size of secret key
.setExpiration(expDate);
}
public static SecretKey generalKey(){
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return Keys.hmacShaKeyFor(encodeKey);
}
Also, add the following dependencies:
For Maven:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
For Gradle:
dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtime 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}

Related

how to set hash in Postman Pre-Request Script for Marvel API

I have a pre-request script that I gathered from another post on StackOverflow, but I'm still getting invalid credentials.
Attempted to do this just with str_1 but it's not working. Not sure what request.data is supposed to do as it keeps returning NaN. I think that the problem might be there, but still at a loss. I've attempted converting all variables to a string, but that still returned the same error.
URL = https://gateway.marvel.com/v1/public/characters?ts={{timeStamp}}&apikey={{apiKey}}&hash={{hash}}
// Access your env variables like this
var ts = new Date();
ts = ts.getUTCMilliseconds();
var str_1 = ts + environment.apiKey + environment.privateKey;
// Or get your request parameters
var str_2 = request.data["timeStamp"] + request.data["apiKey"];
console.log('str_2 = ' + str_2);
// Use the CryptoJS
var hash = CryptoJS.MD5(str_1).toString();
// Set the new environment variable
pm.environment.set('timeStamp', ts);
pm.environment.set('hash', hash);
{
"code": "InvalidCredentials",
"message": "That hash, timestamp and key combination is invalid."
}
If someone can comment on why this is the solution, I would appreciate it. Here is what the issue was. The order of the hash actually matters. So had to flip the order of pvtkey + pubkey to pubkey + pvtkey. Why is this?
INCORRECT
var message = ts+pubkey+pvtkey;
var a = CryptoJS.MD5(message);
pm.environment.set("hash", a.toString());
CORRECT
var message = ts+pvtkey+pubkey;
var a = CryptoJS.MD5(message);
pm.environment.set("hash", a.toString());
I created in Android Studio, a new java class named MD5Hash, following the steps of https://javarevisited.blogspot.com/2013/03/generate-md5-hash-in-java-string-byte-array-example-tutorial.html
I just simplified his (her) code, only to use it with Java utility MessageDigest
public class MD5Hash {
public static void main(String args[]) {
String publickey = "abcdef"; //your api key
String privatekey = "123456"; //your private key
Calendar calendar=Calendar.getInstance();
String stringToHash = calendar
.getTimeInMillis()+ privatekey + publickey;
System.out.println("hash : " + md5Java(stringToHash));
System.out.println("ts : "+ calendar.getTimeInMillis());
}
public static String md5Java(String message){
String digest = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(message.getBytes("UTF-8"));
//converting byte array to Hexadecimal String
StringBuilder sb = new StringBuilder(2*hash.length);
for(byte b : hash){
sb.append(String.format("%02x", b&0xff));
}
digest = sb.toString();
} catch (UnsupportedEncodingException ex) {
} catch (NoSuchAlgorithmException ex) {
}
return digest;
}
}
As you can see, if you copy paste this code, it has a green arrow on the left side of the class declaration, clicking it you can run MD5Hash.main() and you'll have printed in your Run Screen the values for the time (ts) and for the hash.
Then go to verify directly into the internet :
https://gateway.marvel.com/v1/public/characters?limit=20&ts=1574945782067&apikey=abcdef&hash=4bbb5dtf899th5132hjj66

How to use AWSRequestSigningApacheInterceptor with AWS SDK2

I am trying to use REST calls to Neptune SPARQL on existing Java code which already uses Apache HTTP clients. I'd like to not mix and match AWS SDK1 and SDK2 (which I use for the S3 portion of loading owl to Neptune).
I see these solutions:
AWSRequestSigningApacheInterceptor that works with SDK1, but can't find the equivalent in SDK2.
aws-request-signing-apache-interceptor on github for building an adaptor class so it can be used in SDK 2 with mix-and-match SDK 1 & 2
javaquery/Examples where Vicky Thakor has gone even more generic and just implemented the V4 signing for any Java REST implementation
But none of these is what I expected: an AWS or Apache implmentation of an Apache Interceptor for AWS SDK 2.
Is there such a thing? or is one of the above solutions the best available at the moment?
Here is some minimal code to make a few different authenticated REST requests to the ElasticSearch API (not Neptune SPARQL, but it's all REST).
pom.xml:
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<!-- version number is not needed due to the BOM below -->
</dependency>
<!-- below is needed for this issue: https://github.com/aws/aws-sdk-java-v2/issues/652 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.11</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
<!-- version number is not needed due to the BOM below -->
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.7.36</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
And here's the java:
import org.json.JSONObject;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
import software.amazon.awssdk.http.*;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.utils.StringInputStream;
import java.io.*;
public class ElasticSearch implements Closeable {
private static final String HOST = "my-elasticsearch-3490jvoi2je3o.us-east-2.es.amazonaws.com";
private Aws4SignerParams params = Aws4SignerParams.builder()
.awsCredentials(DefaultCredentialsProvider.create().resolveCredentials())
.signingName("es") // "es" stands for elastic search. Change this to match your service!
.signingRegion(Region.US_EAST_2)
.build();
private Aws4Signer signer = Aws4Signer.create();
SdkHttpClient httpClient = ApacheHttpClient.builder().build();
/** #param path should not have a leading "/" */
private HttpExecuteResponse restRequest(SdkHttpMethod method, String path) throws IOException {
return restRequest(method, path, null);
}
private HttpExecuteResponse restRequest(SdkHttpMethod method, String path, JSONObject body)
throws IOException {
SdkHttpFullRequest.Builder b = SdkHttpFullRequest.builder()
.encodedPath(path)
.host(HOST)
.method(method)
.protocol("https");
if (body != null) {
b.putHeader("Content-Type", "application/json; charset=utf-8");
b.contentStreamProvider(() -> new StringInputStream(body.toString()));
}
SdkHttpFullRequest request = b.build();
// now sign it
SdkHttpFullRequest signedRequest = signer.sign(request, params);
HttpExecuteRequest.Builder rb = HttpExecuteRequest.builder().request(signedRequest);
// !!!: line below is necessary even though the contentStreamProvider is in the request.
// Otherwise the body will be missing from the request and auth signature will fail.
request.contentStreamProvider().ifPresent(c -> rb.contentStreamProvider(c));
return httpClient.prepareRequest(rb.build()).call();
}
public void search(String indexName, String searchString) throws IOException {
HttpExecuteResponse result = restRequest(SdkHttpMethod.GET, indexName+"/_search",
new JSONObject().put("query",
new JSONObject().put("match",
new JSONObject().put("txt",
new JSONObject().put("query", searchString)))));
System.out.println("Search results:");
System.out.println(new JSONObject(result.responseBody()));
}
/** #return success status */
public boolean createIndex(String indexName) throws IOException {
if (indexName.contains("/")) {
throw new RuntimeException("indexName cannot contain '/' character");
}
HttpExecuteResponse r = restRequest(SdkHttpMethod.PUT, indexName);
System.out.println("PUT /"+indexName + " response code: " + r.httpResponse().statusCode());
printInputStream(r.responseBody().get());
return r.httpResponse().isSuccessful();
}
private void printInputStream(InputStream is) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String readLine;
while (((readLine = br.readLine()) != null)) System.out.println(readLine);
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean postDoc(String indexName, String docId, JSONObject docBody) throws IOException {
HttpExecuteResponse response = restRequest(
SdkHttpMethod.PUT,
String.format("%s/_doc/%s", indexName, docId),
docBody
);
System.out.println("Index operation response:");
printInputStream(response.responseBody().get());
return response.httpResponse().isSuccessful();
}
#Override
public void close() throws IOException {
httpClient.close();
}
}
So, I settled on the second option with an important caveat: it does not handle AWS_SESSION_TOKEN. This is a simple fix. I've posted it along with the original answer at http://github.com/awslabs/aws-request-signing-apache-interceptor/
There's a new maintained fork of the archived awslabs aws-request-signing-apache-interceptor. It was upgraded to AWS SDK 2, and has a number of bug fixes, such as supporting retries. Version 2.1.1 was just released to Maven central.

AWS Signature Creation. Confused on how to covert the SigningKey and SingingString into Signature. AWS Example Seems To Not Yield Expected Result

I'm trying to write my own AWS4 signer, and I've gotten about 2/3 of the way there. Source code here :
public class Test
{
private static String region = "us-east-1";
static byte[] HmacSHA256(String data, byte[] key) throws Exception {
String algorithm="HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF8"));
}
public static byte[] justSha256(String data) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes("UTF8"));
return hash;
}
static byte[] getSigningKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}
public static String getSimpleDate()
{
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMMdd");
return LocalDate.now().format(formatter);
}
public static String getAMZDate()
{
/*DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMMDDHHMMSS");
String timeStamp = new SimpleDateFormat("YYMMDD'T'HHMMSS'Z'").format(Calendar.getInstance().getTime());*/
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
df.setTimeZone(tz);
String timeStamp = df.format(new Date());
return timeStamp;
}
public static String createSigningString(String timeStamp, String simpleDate,String serviceName) throws UnsupportedEncodingException, NoSuchAlgorithmException {
/*AWS4-HMAC-SHA256
20150830T123600Z
20150830/us-east-1/iam/aws4_request
f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59*/
String algorithm = "AWS4-HMAC-SHA256\n";
String amzDate = timeStamp+"\n";
String simpleDateRegionServiceRequest = simpleDate+"/"+region+"/"+serviceName+"/"+"aws4_request\n";
String canonicalHash = getCanonicalHash(getCanonicalString("GET","/","Action=ListUsers&Version=2010-05-08","20150830T123600Z",""));
String signingString = algorithm+amzDate+simpleDateRegionServiceRequest+canonicalHash;
return signingString;
}
public static String getCanonicalString(String method, String absolutePath, String queryString, String timeStamp, String payload) throws UnsupportedEncodingException, NoSuchAlgorithmException {
String contentType = "Content-Type:application/x-www-form-urlencoded; charset=utf-8\n".toLowerCase();
String hostUrl = "host:iam.amazonaws.com\n";
String date = "x-amz-date:"+timeStamp+"\n";
String signedHeader = "content-type;host;x-amz-date\n";
String hashedPayload = Hex.encodeHexString(justSha256(payload)).toLowerCase();
String canonicalString = method+"\n"+absolutePath+"\n"+queryString+"\n"+contentType+hostUrl+date+"\n"+signedHeader+hashedPayload;
return canonicalString;
}
public static String getCanonicalHash(String canonicalString) throws UnsupportedEncodingException, NoSuchAlgorithmException {
return Hex.encodeHexString(justSha256(canonicalString)).toLowerCase();
}
public static void main(String[] args) throws Exception {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYYMMDDHHMMSS");
String timeStamp = new SimpleDateFormat("YYYYMMDD'T'HHMMSS'Z'").format(Calendar.getInstance().getTime());
String canonString;
System.out.println( canonString = getCanonicalHash(getCanonicalString("GET","/","Action=ListUsers&Version=2010-05-08","20150830T123600Z","")));
String signingString = createSigningString("20150830T123600Z","20150830","iam");
String key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
String dateStamp = "20120215";
String regionName = "us-east-1";
String serviceName = "iam";
String signingKey = Hex.encodeHexString(getSigningKey(key,dateStamp,regionName,serviceName));
SoftAssertions softly = new SoftAssertions();
softly.assertThat(canonString).isEqualToIgnoringCase("f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59");
String copiedSigningString = "AWS4-HMAC-SHA256\n" +
"20150830T123600Z\n" +
"20150830/us-east-1/iam/aws4_request\n" +
"f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59";
softly.assertThat(signingString).isEqualTo(copiedSigningString);
softly.assertThat(signingKey).isEqualToIgnoringCase("f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d");
dateStamp ="20150830";
signingKey = Hex.encodeHexString(getSigningKey(key,dateStamp,regionName,serviceName));
softly.assertThat(signingKey).isEqualToIgnoringCase("c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9");
System.out.println("COPIED STRING : "+copiedSigningString);
System.out.println("SIGNING KEY : "+signingKey);
String signature = Hex.encodeHexString(HmacSHA256(signingKey.trim(),justSha256(copiedSigningString)));
System.out.println("Signature : "+signature);
softly.assertThat(signature).isEqualToIgnoringCase("5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7");
softly.assertAll();
}
}
For some reason I'm failing to create the correct Signature. Which is created by using the SigningKey and SingingString as input into an HMACSha256 function, specifically the function highlighted on this page https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java.
But for some reason, I cannot produce the signature that AWS says will be created here on this page.: https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
(5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7)
Despite using their function, their Strings as input, and double checking my work. What am I missing?
Are you supposed to use a different function for these Strings or byte[]?
I feel like I'm missing something, and I don't know where to reconcle this confusion because from my code and what I see on these pages I seem to be doing the right thing.
I'm especially confused because when I pasted the String from their site into my code to see if it would convert properly it did not, despite using their same Hmac function (which did work for creating every other hash string posted on their site).
The hex representation of the date, region, service, and signing keys is shown for illustration, because the keys contain bytes that do not represent printable characters.
But you appear to be hex-encoding your signing key before using it to sign the request. Don't do that. You will want to hex-encode it only for viewing/debugging. The actual signing key should be retained and used in its original binary/byte form.
Use the digest (binary format) for the key derivation. Most languages have functions to compute either a binary format hash, commonly called a digest, or a hex-encoded hash, called a hexdigest. The key derivation requires that you use a binary-formatted digest.
https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
The canonical request hash in the string-to-sign is used in hex-encoded form, as is the final signature. The key derivation is all binary.

aws s3 - browser upload using post - gwt & java

I am working on a wgt webapp and would like to upload files to s3 from the browser.
Since my credentials are on the server, I need to create the signature on the server and send it to the client to be able to ulpoad.
here is the code I am using:
-dateStamp is in the right format - yyyyMMdd
-policy is base64 encoded - I double checked that
public static String getSignatureForS3Upload(final String dateStamp, final String policy) {
byte[] signingKey = null;
byte[] signature = null;
String strSignature = null;
try {
signingKey = AwsUtill.getSignatureKey(AppConfig.getS3SecretKey(), dateStamp,
AppConfigShared.getMyAwsS3RegionName(), "s3");
signature = HmacSHA256(policy, signingKey);
strSignature = bytesToHex(signature);
}
catch (Exception e) {
// log
}
ServerDBLogger.log(Level.INFO, byteArrayToHex(signature));
ServerDBLogger.log(Level.INFO, bytesToHex(signature));
return strSignature;
private static byte[] HmacSHA256(final String data, final byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF-8"));
}
private static byte[] getSignatureKey(final String key, final String dateStamp, final String regionName,
final String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(final byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
With the genarated signature I get an error message:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
What am I doing wrong?
Is there any good tutorial or sample code to do this?
Thank you!
There have been changes to the AWS signature process - the version at this time (May 2016) is AWS Signature version 4 (http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). You need to be careful when looking at examples and Stackoverflow etc that the information relates to the same signature version you are using as they don't mix well.
I started using the AWS SDK for an angular/node file upload but eventually found it easier to generate the policy on the server (node.js) side without the SDK. There is a good example (albeit node based which may not be what you are looking for) here: https://github.com/danialfarid/ng-file-upload/wiki/Direct-S3-upload-and-Node-signing-example (but note the issue with the S3 bucket name here: AngularJs Image upload to S3 ).
One key thing to watch is that you correctly include the file content type in the policy generation and that this content type properly matches the content type of the file you are actually uploading.

Modify response of web service with JAX-WS

How can I modify the namespace of the response like this:
old response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:GetAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</ns2:GetAmountResponse>
</soap:Body>
</soap:Envelope>
new response wanted :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
</soap:Body>
</soap:Envelope>
I want to remove the ns2 namespce prefix.
In the first case, the GetAmountResponse is in namespace http://ws.dsi.otn.com/dab while etat and montant are in a default (empty) namespace.
In the new message you want, GetAmountResponse, etat and montant are all in namespace http://ws.dsi.otn.com/dab.
The namespaces can be controlled from the namespaces of your classes. Use the same namespace in all and you will have them in the same namespace, leave classes with defaults and they default to empty namespace.
For example, if you were to have something like this in your web service class:
#WebMethod
public
#WebResult(name = "getAmountResponse", targetNamespace = "http://ws.dsi.otn.com/dab")
AmountResponse getAmount(
#WebParam(name = "getAmountRequest", targetNamespace = "http://ws.dsi.otn.com/dab") AmountRequest request) {
AmountResponse response = new AmountResponse();
response.setEtat(0);
response.setMontant(500.0);
return response;
}
with a response class like this:
#XmlRootElement
public class AmountResponse {
private int etat;
private double montant;
// getter and setters omitted
}
you will end up with the first type of soap message.
But if you change the response class to look like this instead:
#XmlRootElement(namespace = "http://ws.dsi.otn.com/dab")
#XmlAccessorType(XmlAccessType.NONE)
public class AmountResponse {
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private int etat;
#XmlElement(namespace = "http://ws.dsi.otn.com/dab")
private double montant;
// getters and setter omitted
}
you will bring all tags in the same namespace and you get something equivalent to the new type of message you want. I said equivalent because I don't think you will get exactly this:
<GetAmountResponse xmlns="http://ws.dsi.otn.com/dab">
<etat>0</etat>
<montant>500.0</montant>
</GetAmountResponse>
It's more likely to get something like this instead:
<ns2:getAmountResponse xmlns:ns2="http://ws.dsi.otn.com/dab">
<ns2:etat>0</ns2:etat>
<ns2:montant>500.0</ns2:montant>
</ns2:getAmountResponse>
It's the same "XML meaning" for both messages although they don't look the same.
If you absolutely want it to look like that, I think you will have to go "low level" and use something like a SOAP handler to intercept the response and modify it. But be aware that it won't be a trivial task to change the message before it goes on the wire.
logical handler are enough to transform to the message as expected :
package com.ouertani.slim;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;
/**
*
* #author ouertani
*/
public class MyLogicalHandler implements LogicalHandler<LogicalMessageContext> {
#Override
public boolean handleMessage(LogicalMessageContext messageContext) {
/// extract state and amount
int state = 0;
double amount = 200.0;
transform(messageContext, state, amount);
return false;
}
public boolean handleFault(LogicalMessageContext messageContext) {
return true;
}
public void close(MessageContext context) {
}
private void transform( LogicalMessageContext messageContext, int etat, double montant){
LogicalMessage msg = messageContext.getMessage();
String htom = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<soap:Body>"+
"<GetAmountResponse xmlns=\"http://ws.dsi.otn.com/dab\">"+
"<etat>"+etat+"</etat>"+
"<montant>"+montant+"</montant>"+
"</GetAmountResponse>"+
"</soap:Body>"+
"</soap:Envelope>";
InputStream is = new ByteArrayInputStream(htom.getBytes());
Source ht = new StreamSource(is);
msg.setPayload(ht);
}
}
This is a very old question, still it is yet to be effectively answered. This week I faced a very similar problem. My application is invoking a Soap web-service provided by a legacy system whose XML is response syntactically wrong with some empty characters (line break, or tabs or white spaces) before XML declaration. In my scenario I could not change the legacy system to fix its response so changing the response before parsing was the only alternative I was left with.
Here is my solution:
I have added the following maven dependencies to my application:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.0</version>
</dependency>
Then I have registered a Java SPI custom implementation of “com.oracle.webservices.impl.internalspi.encoding.StreamDecoder”. This class is invoked immediately before the XML parse with the corresponding response InputStream, so at this point you can read the response InputStream or wrap/proxy it and make any change to jax-ws response before parsing. In my case I just remove some invisible characters before first visible character.
My StreamDecoder SPI implementation:
package sample.streamdecoder;
import com.oracle.webservices.impl.encoding.StreamDecoderImpl;
import com.oracle.webservices.impl.internalspi.encoding.StreamDecoder;
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.message.AttachmentSet;
import com.sun.xml.ws.api.message.Message;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
public class MyStreamDecoder implements StreamDecoder {
//JAX-WS default implementation
private static final StreamDecoderImpl streamDecoder = new StreamDecoderImpl();
#Override
public Message decode(InputStream inputStream, String charset, AttachmentSet attachmentSet, SOAPVersion soapVersion) throws IOException {
//Wrapping inputStream
InputStream wrapped = wrapInputStreamStrippingBlankCharactersBeforeXML(inputStream, charset);
//Delegating further processing to default StreamDecoder
return streamDecoder.decode(wrapped, charset, attachmentSet, soapVersion);
}
private InputStream wrapInputStreamStrippingBlankCharactersBeforeXML(InputStream inputStream, String charset) throws IOException {
int WHITESPACE = (int) Charset.forName(charset).encode(" ").get();
int LINE_BREAK = (int) Charset.forName(charset).encode("\n").get();
int TAB = (int) Charset.forName(charset).encode("\t").get();
return new InputStream() {
private boolean xmlBegin = true;
#Override
public int read() throws IOException {
int read = inputStream.read();
if (!xmlBegin) {
return read;
} else {
while (WHITESPACE == read
|| LINE_BREAK == read
|| TAB == read) {
read = inputStream.read();
}
xmlBegin = false;
}
return read;
}
};
}
}
In order to register it, just create a file “META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder” named “” and write the fully qualified name of your SPI implementation on the first line like that:
Content of file META-INF/services/ com.oracle.webservices.impl.internalspi.encoding.StreamDecoder :
sample.streamdecoder.MyStreamDecoder
Now every response will be passed to you implementation before parse.