The situation is as follows:
On the one side I have created an OData-Service which should create an entry when it receives a POST-Request. The Service is created in an S/4HANA System and is reachable via the SAP-Gateway.
On the other hand I have a Java Application (OpenJDK 11) which does essentially a loop and must issue every loop a POST-Request to the OData-Service.
I'm using IntelliJ IDEA Community Edition and OpenJDK 11.
Also this is my first time using OData both with Java and SAP.
At first I tried the following:
private static void postRequest() throws IOException {
//Setting authenticator needed for login
Authenticator authenticator = new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
};
Authenticator.setDefault(authenticator);
//Creating the connection
URL url = new URL("<my_service_link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json; utf-8");
con.setRequestProperty("Accept", "application/json");
con.setDoOutput(true);
try(OutputStream os = con.getOutputStream()) {
byte[] input = this.getJsonRequest().getBytes("utf-8");
os.write(input, 0, input.length);
}
//Reading response
int status = con.getResponseCode();
Reader streamReader = null;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
} else {
streamReader = new InputStreamReader(con.getInputStream());
}
BufferedReader in = new BufferedReader(streamReader);
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
}
But I got the error, that my CSRF-Token is invalid.
So after googling to find out what an CSRF-Token is I tried to create a GET-Request first with its own HttpsURLConnection:
private static String getRequest() {
//Setting authenticator needed for login
Authenticator authenticator = new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
};
Authenticator.setDefault(authenticator);
//Creating the connection
URL url = new URL("<my_service_link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json; utf-8");
con.setRequestProperty("X-CSRF-Token","fetch");
con.connect();
return con.getHeaderField("x-csrf-token").toString();
}
Then I would issue the actual POST-Request to the same URL and set the previous X-CSRF-Token into the HTTPS-Header with
con.setRequestProperty("X-CSRF-Token",theGETToken); in postRequest()
But I still got the same error.
What am I doing wrong?
After some more googling I eventually understood what I was missing.
The CSRF-Token is only valid for a specific session of a user. The session is identified by the cookies passed in the HTTPS-Header.
What needs to be done is the following (also see: https://blogs.sap.com/2021/06/04/how-does-csrf-token-work-sap-gateway/):
Open a session by issuing a non-modification request and specify the header to fetch a CSRF-Token and session-cookies
HTTP-Request:
Type: GET
Header-Fields: x-csrf-token = fetch
set-cookie = fetch
Save the CSRF-Token and session-cookies as you need them for the POST-Request
Issue a POST-Request and set the session-cookies and CSRF-Token from the saved values
HTTP-Request:
Type: POST
Header-Fields: x-csrf-token = <tokenFromGet>
cookie = <allSessionCookies>
Beware that the header field of a request is named cookie instead of set-cookie and to pass all values of the HeaderField of set-cookie to the POST-Request-Header.
It is also important to mention, that the CSRF-Token as well as the session-cookies expire after a provided or adjusted timeframe or any changes to the session are made and both must be fetched anew (see https://blogs.sap.com/2021/06/04/how-does-csrf-token-work-sap-gateway/#comment-575524).
Example of my Working Code:
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
public class ODataLogger {
private String sessionCookies;
private String csrfToken;
public ODataLogger() {}
public void logOdata (String user, String pass, String jsonBody) throws IOException {
this.setDefaultAuthenticator(user, pass);
fetchSessionHeaderFields();
postRequest(jsonBody);
}
private void setDefaultAuthenticator (String user, String pass) {
Authenticator authenticator = new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pass.toCharArray());
}
};
Authenticator.setDefault(authenticator);
}
private void fetchSessionHeaderFields() throws IOException {
URL url = new URL("<my-service-link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("x-csrf-token", "fetch");
con.setRequestProperty("set-cookie","fetch");
//Reading Response
int status = con.getResponseCode();
Reader streamReader = null;
if (status < 299) {
StringBuffer sb = new StringBuffer(con.getHeaderFields().get("set-cookie").toString());
//Delete leading [ and trailing ] character
sb.deleteCharAt(this.sessionCookies.length()-1);
sb.deleteCharAt(0);
this.sessionCookies = sb.toString();
this.csrfToken = con.getHeaderField("x-csrf-token");
return;
}
}
private void postRequest(String jsonBody) throws IOException {
//Creating the connection
URL url = new URL("<my-service-link>");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("x-csrf-token", this.csrfToken);
con.setRequestProperty("Cookie", this.sessionCookies);
con.setRequestProperty("Accept", "application/json");
//Setting JSON Body
con.setDoOutput(true);
try(OutputStream os = con.getOutputStream()) {
byte[] input = jsonBody.getBytes("utf-8");
os.write(input, 0, input.length);
}
//Reading response
int status = con.getResponseCode();
Reader streamReader = null;
if (status > 299) {
streamReader = new InputStreamReader(con.getErrorStream());
} else {
streamReader = new InputStreamReader(con.getInputStream());
}
BufferedReader in = new BufferedReader(streamReader);
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
i have API service on my website to connect with other host like https://otherhost.com
when i test it on localhost it working and return correct response
but when i deployed to server it return error java.net.ConnectException: Connection timed out (Connection timed out)
Noted:
in localhost and server use the same url > https://otherhost.com
i already use command ping, traceroute, telnet and it can find otherhost perfectly
i already add ip and hostname to my docker compose /etc/hosts file
Sample of my code
String host = "otherhost.com";
URL url = " https://otherhost.com/api/GetAccess.php?grant_type=authorization&scope=openid";
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
SSLContext.setDefault(ctx);
// start connection...
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
Set Header...
con.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
String authStr = env.getProperty("authen");
String base64Creds = Base64.getEncoder().encodeToString(authStr.getBytes());
con.setRequestProperty("Host", host);
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("Authorization", String.format("Basic %s", base64Creds));
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestMethod("GET");
System.out.println("Status Code :: " + con.getResponseCode());
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
System.out.println("response :: " + responseLine);
response.append(responseLine.trim());
}
con.disconnect();
I am trying to make an http request with proxy using Java and get the response code in an integer variable.
The method I am using is:
public static int getResponseCode(String urlString, Proxy p) throws MalformedURLException, IOException{
URL url = new URL(urlString);
HttpResponse urlresp = new DefaultHttpClient().execute(new HttpGet(urlString));
int resp_Code = urlresp.getStatusLine().getStatusCode();
return resp_Code;
}
I am passing a proxy parameter but I am not sure how to use it while making the request. I tried to look up resources online but was unable to find an apt solution.
I tried the below solution but was unsure how to get the response code here:
HttpHost proxy = new HttpHost("proxy.com", 80, "http");
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
You could try this:
URL url = new URL("http://www.myurl.com");
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.com", 8080));
// IF NEED AUTH
// Authenticator authenticator = new Authenticator() {
// public PasswordAuthentication getPasswordAuthentication() {
// return (new PasswordAuthentication("username",
// "password".toCharArray()));
// }
// };
// Authenticator.setDefault(authenticator);
HttpURLConnection conn = (HttpURLConnection)url.openConnection(proxy);
conn.setRequestMethod("GET");
conn.connect();
int code = conn.getResponseCode();
System.out.println(code);
url = new URL(UPLOAD_URL);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("PUT");
urlConnection.setRequestProperty("content-type", "application/json");
urlConnection.setFixedLengthStreamingMode(responseJSONArray.toString(2).getBytes("UTF8").length);
urlConnection.setDoInput(false);
urlConnection.setDoOutput(true);
urlConnection.setConnectTimeout(this.CONNECT_TIMEOUT);
urlConnection.connect();
OutputStream output = urlConnection.getOutputStream();
output.write(responseJSONArray.toString(2).getBytes("UTF8"));
output.close();
I've also already earlier set the Authenticator with:
Authenticator.setDefault(new Authenticator()
{
#Override
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication(loginNameString, passwordString.toCharArray());
}
});
I supply correct login details, but the server responds with a 401 code. (A similar GET-request works though.) On top of which, the method getPasswordAuthentication() is not being called in the process of connecting and writing to the stream. (I know this because I put in Log.v("app", "password here").)
Why is that?
I'm not able to answer to why using Authenticator does not work, but I usually use this approach:
String webPage = "http://192.168.1.1";
String name = "admin";
String password = "admin";
String authString = name + ":" + password;
byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
String authStringEnc = new String(authEncBytes);
URL url = new URL(webPage);
URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
Try it. Using basic auth should be enough.
How do I connect to a remote URL in Java which requires authentication. I'm trying to find a way to modify the following code to be able to programatically provide a username/password so it doesn't throw a 401.
URL url = new URL(String.format("http://%s/manager/list", _host + ":8080"));
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
There's a native and less intrusive alternative, which works only for your call.
URL url = new URL(“location address”);
URLConnection uc = url.openConnection();
String userpass = username + ":" + password;
String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
uc.setRequestProperty ("Authorization", basicAuth);
InputStream in = uc.getInputStream();
You can set the default authenticator for http requests like this:
Authenticator.setDefault (new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication ("username", "password".toCharArray());
}
});
Also, if you require more flexibility, you can check out the Apache HttpClient, which will give you more authentication options (as well as session support, etc.)
You can also use the following, which does not require using external packages:
URL url = new URL(“location address”);
URLConnection uc = url.openConnection();
String userpass = username + ":" + password;
String basicAuth = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(userpass.getBytes());
uc.setRequestProperty ("Authorization", basicAuth);
InputStream in = uc.getInputStream();
If you are using the normal login whilst entering the username and password between the protocol and the domain this is simpler. It also works with and without login.
Sample URL: http://user:pass#example.com/url
URL url = new URL("http://user:pass#example.com/url");
URLConnection urlConnection = url.openConnection();
if (url.getUserInfo() != null) {
String basicAuth = "Basic " + new String(new Base64().encode(url.getUserInfo().getBytes()));
urlConnection.setRequestProperty("Authorization", basicAuth);
}
InputStream inputStream = urlConnection.getInputStream();
Please note in the comment, from valerybodak, below how it is done in an Android development environment.
As I have came here looking for an Android-Java-Answer I am going to do a short summary:
Use java.net.Authenticator as shown by James van Huis
Use Apache Commons HTTP Client, as in this Answer
Use basic java.net.URLConnection and set the Authentication-Header manually like shown here
If you want to use java.net.URLConnection with Basic Authentication in Android try this code:
URL url = new URL("http://www.example.com/resource");
URLConnection urlConnection = url.openConnection();
String header = "Basic " + new String(android.util.Base64.encode("user:pass".getBytes(), android.util.Base64.NO_WRAP));
urlConnection.addRequestProperty("Authorization", header);
// go on setting more request headers, reading the response, etc
Was able to set the auth using the HttpsURLConnection
URL myUrl = new URL(httpsURL);
HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
String userpass = username + ":" + password;
String basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
//httpsurlconnection
conn.setRequestProperty("Authorization", basicAuth);
few of the changes fetched from this post. and Base64 is from java.util package.
Be really careful with the "Base64().encode()"approach, my team and I got 400 Apache bad request issues because it adds a \r\n at the end of the string generated.
We found it sniffing packets thanks to Wireshark.
Here is our solution :
import org.apache.commons.codec.binary.Base64;
HttpGet getRequest = new HttpGet(endpoint);
getRequest.addHeader("Authorization", "Basic " + getBasicAuthenticationEncoding());
private String getBasicAuthenticationEncoding() {
String userPassword = username + ":" + password;
return new String(Base64.encodeBase64(userPassword.getBytes()));
}
Hope it helps!
Use this code for basic authentication.
URL url = new URL(path);
String userPass = "username:password";
String basicAuth = "Basic " + Base64.encodeToString(userPass.getBytes(), Base64.DEFAULT);//or
//String basicAuth = "Basic " + new String(Base64.encode(userPass.getBytes(), Base64.No_WRAP));
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setRequestProperty("Authorization", basicAuth);
urlConnection.connect();
Since Java 9, you can do this
URL url = new URL("http://www.example.com");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setAuthenticator(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication ("USER", "PASS".toCharArray());
}
});
I'd like to provide an answer for the case that you do not have control over the code that opens the connection. Like I did when using the URLClassLoader to load a jar file from a password protected server.
The Authenticator solution would work but has the drawback that it first tries to reach the server without a password and only after the server asks for a password provides one. That's an unnecessary roundtrip if you already know the server would need a password.
public class MyStreamHandlerFactory implements URLStreamHandlerFactory {
private final ServerInfo serverInfo;
public MyStreamHandlerFactory(ServerInfo serverInfo) {
this.serverInfo = serverInfo;
}
#Override
public URLStreamHandler createURLStreamHandler(String protocol) {
switch (protocol) {
case "my":
return new MyStreamHandler(serverInfo);
default:
return null;
}
}
}
public class MyStreamHandler extends URLStreamHandler {
private final String encodedCredentials;
public MyStreamHandler(ServerInfo serverInfo) {
String strCredentials = serverInfo.getUsername() + ":" + serverInfo.getPassword();
this.encodedCredentials = Base64.getEncoder().encodeToString(strCredentials.getBytes());
}
#Override
protected URLConnection openConnection(URL url) throws IOException {
String authority = url.getAuthority();
String protocol = "http";
URL directUrl = new URL(protocol, url.getHost(), url.getPort(), url.getFile());
HttpURLConnection connection = (HttpURLConnection) directUrl.openConnection();
connection.setRequestProperty("Authorization", "Basic " + encodedCredentials);
return connection;
}
}
This registers a new protocol my that is replaced by http when credentials are added. So when creating the new URLClassLoader just replace http with my and everything is fine. I know URLClassLoader provides a constructor that takes an URLStreamHandlerFactory but this factory is not used if the URL points to a jar file.
i did that this way you need to do this just copy paste it be happy
HttpURLConnection urlConnection;
String url;
// String data = json;
String result = null;
try {
String username ="user#gmail.com";
String password = "12345678";
String auth =new String(username + ":" + password);
byte[] data1 = auth.getBytes(UTF_8);
String base64 = Base64.encodeToString(data1, Base64.NO_WRAP);
//Connect
urlConnection = (HttpURLConnection) ((new URL(urlBasePath).openConnection()));
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "Basic "+base64);
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestMethod("POST");
urlConnection.setConnectTimeout(10000);
urlConnection.connect();
JSONObject obj = new JSONObject();
obj.put("MobileNumber", "+97333746934");
obj.put("EmailAddress", "danish.hussain#example.com");
obj.put("FirstName", "Danish");
obj.put("LastName", "Hussain");
obj.put("Country", "BH");
obj.put("Language", "EN");
String data = obj.toString();
//Write
OutputStream outputStream = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
writer.write(data);
writer.close();
outputStream.close();
int responseCode=urlConnection.getResponseCode();
if (responseCode == HttpsURLConnection.HTTP_OK) {
//Read
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
bufferedReader.close();
result = sb.toString();
}else {
// return new String("false : "+responseCode);
new String("false : "+responseCode);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
ANDROD IMPLEMENTATION
A complete method to request data/string response from web service requesting authorization with username and password
public static String getData(String uri, String userName, String userPassword) {
BufferedReader reader = null;
byte[] loginBytes = (userName + ":" + userPassword).getBytes();
StringBuilder loginBuilder = new StringBuilder()
.append("Basic ")
.append(Base64.encodeToString(loginBytes, Base64.DEFAULT));
try {
URL url = new URL(uri);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("Authorization", loginBuilder.toString());
StringBuilder sb = new StringBuilder();
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine())!= null){
sb.append(line);
sb.append("\n");
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (null != reader){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}