Shelly 2pm (Gen 2 Gen3) Authentication-Problem

hajueschmitz

Mitglied
JDK 17, Zugriff auf shelly, Authentication, java.net.*

Es geht um Zugriff via http, konkret die URL. Nichts wirklich Hilfeiches bei Google gefunden.
In Shellyforen wieder keine Javakenntnisse (nur javascript)

"http://admin:passwort@192.168.178.146/rpc/Shelly.GetStatus"

Java Code:
URL url;
URLConnection con;
url = new URL(urlstr);
con=url.openConnection();

Solange am Shelly Authentication ausgeschaltet ist funktioniert das wunderbar.

MIT Authentication folgt ein "Error 401".

mit "curl" auf dem Radpberry Pi läuft die URL: (wie kriege ich das mit Java hin??)


curl http://admin:******@192.168.178.146/rpc/Shelly.GetStatus -v --digest
  • Trying 192.168.178.146:80...
  • Connected to 192.168.178.146 (192.168.178.146) port 80 (#0)
  • Server auth using Digest with user 'admin'
GET /rpc/Shelly.GetStatus HTTP/1.1
Host: 192.168.178.146
User-Agent: curl/7.88.1
Accept: /
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Digest qop="auth", realm="shelly2pmg3-e4b3231d66d4", nonce="1763706843", algorithm=SHA-256
< Content-Length: 0
< Server: ShellyHTTP/1.0.0
< Connection: close
<
  • Closing connection 0
  • Issue another request to this URL: 'http://admin:******@192.168.178.146/rpc/Shelly.GetStatus'
  • Hostname 192.168.178.146 was found in DNS cache
  • Trying 192.168.178.146:80...
  • Connected to 192.168.178.146 (192.168.178.146) port 80 (#1)
  • Server auth using Digest with user 'admin'
GET /rpc/Shelly.GetStatus HTTP/1.1
Host: 192.168.178.146
Authorization: Digest username="admin", realm="shelly2pmg3-e4b3231d66d4", nonce="1763706843", uri="/rpc/Shelly.GetStatus", cnonce="M2MwNTFlODRkZTBhMzA5N2I2OGQ3MTY5OGQwMTUxYmQ=", nc=00000001, qop=auth, response="9d52116c4807585f4d728118e5628e806bebd39d655765f06d5e6d6a856fdc6d", algorithm=SHA-256
User-Agent: curl/7.88.1
Accept: /
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 1456
< Server: ShellyHTTP/1.0.0
< Connection: close
<
{"ble":{},"bthome":{}, .....

usw usf korrekter json Output.


*******
Danke für Hinweise!
 

hajueschmitz

Mitglied
erst mal vielen Dank für die rasche Antwort.

Wenn ich den Text bei baeldung richtig verstanden habe läuft das so:

  • Base64-Encoding der userdaten
  • login [connection.setRequestProperty("Authorization", authHeaderValue);]

und danach ggf. Abfragen.

Ich werde damit einmal experimentieren: in meinem Fall muss die Abfrageurl beim "openconnection" gleich mit übergeben werden.

Leider will der Shelly das in jeder einzelnen Abfrage so:
"http://admin:passwort@192.168.178.146/rpc/Shelly.GetStatus"

also nicht zuerst login und danach div. Abfragen.

Es stellt sich also die Frage "wie baue ich den verschlüsselten String in die URL ein?

Eine Funktion für den 1.Teil hätte ich sogar hier:
public String authcon (String name, String pwd) {
//java.util.Base64.getEncoder = new java.util.Base64();
// BASE64Encoder encoder = new BASE64Encoder();
String authString = name + ":" + pwd;
byte[] bb=authString.getBytes();
String authEncBytes =Base64.getEncoder().encodeToString(bb);
return(authEncBytes);
}
 

hajueschmitz

Mitglied
Shelly Generation 2 (Plus & Pro Serie) nutzen Digest Authentication(Challenge-Response-Verfahren). Das ist sicherer, macht es aber fast unmöglich, sich "einfach so" über die URL im Browser/App anzumelden. Wie der Login funktioniert ist in der Doku beschrieben.
Danke. Angeschaut. Javaskript.
Die Frage ist: Wir wird (nach 401) Schritt 3 "Requesting protected resource with credentials." umgesetzt in Java?
Da sind als Beispiele curl Aufrufe. Ich muß das über die "URLConnection" erledigen.

Zum Browser: "macht es aber fast unmöglich, sich "einfach so" über die URL im Browser/App anzumelden."
Gerade im Browser geht es "wunderbar".
Der Browser fragt nach user/pwd. Gibt man ein. Dann bietet er an diese Daten zu speichern.
Sagt man "ja" gehts es wie bekannt weiter ohne weiteres pwd.

Das genannte Problem habe ich nur in meiner Javasoftware. Also liegts vermutlich an meiner Software.

Die exakt selbe URL aus dem Java-Programm (mit admin:pwd@ip) funktioniert im Browser ohne Fehlermeldung.
(Firefox/Edge)

Ich habe auch den subjektiven Eindruck, dass der Login ne Weile gilt auch ohne Speichern. (wäre genau zu verifizieren)
 

Oneixee5

Top Contributor
Da ich das Gerät nicht hier habe kann ich dir nur ein ungetestetes Beispiel aus der KI posten:
Java:
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;

public class ShellyDigestAuthExample {
  
    public static void main(String[] args) throws Exception {
        String url = "http://dein-shelly-ip/rpc/Shelly.GetStatus";
        String username = "deinBenutzer";
        String password = "deinPasswort";

        BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(
                new AuthScope("dein-shelly-ip", 80),
                new UsernamePasswordCredentials(username, password.toCharArray())
        );

        try (CloseableHttpClient httpclient = HttpClients.custom()
                .setDefaultCredentialsProvider(credsProvider)
                .build()) {
            HttpGet httpget = new HttpGet(url);
            try (CloseableHttpResponse response = httpclient.execute(httpget)) {
                System.out.println(response.getCode() + " " + response.getReasonPhrase());
                System.out.println(new String(response.getEntity().getContent().readAllBytes()));
            }
        }
    }
}

XML:
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>

Beispiel mit URLConnection:
Java:
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ShellyDigestAuthURLConnection {
    public static void main(String[] args) throws Exception {
        String urlStr = "http://dein-shelly-ip/rpc/Shelly.GetStatus";
String username = "deinBenutzer";
String password = "deinPasswort";

URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

// Erster Request (ohne Auth) -> 401 erwartet
        int code = conn.getResponseCode();
if (code == 401) {
            String authHeader = conn.getHeaderField("WWW-Authenticate");
// Challenge-Parameter extrahieren
            Pattern p = Pattern.compile("nonce=\"([^\"]+)\", realm=\"([^\"]+)\", qop=\"([^\"]+)\"");
Matcher m = p.matcher(authHeader);
if (m.find()) {
                String nonce = m.group(1);
String realm = m.group(2);
String qop = m.group(3);
String uri = url.getPath();
String nc = "00000001";
String cnonce = Base64.getEncoder().encodeToString("random".getBytes());

String ha1 = md5(username + ":" + realm + ":" + password);
String ha2 = md5("GET:" + uri);
String response = md5(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2);

String auth = String.format(
                        "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", " +
"qop=%s, nc=%s, cnonce=\"%s\", response=\"%s\"",
username, realm, nonce, uri, qop, nc, cnonce, response);

// Zweiter Request mit Auth-Header
                conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", auth);

int code2 = conn.getResponseCode();
InputStream is = conn.getInputStream();
String body = new String(is.readAllBytes());
System.out.println(code2 + " " + conn.getResponseMessage());
System.out.println(body);
}
        }
    }

    private static String md5(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(s.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
}
}
  • Für produktiven Einsatz sollte ein sicherer Zufallswert für cnonce verwendet werden.
  • Die Implementierung ist ein Minimalbeispiel und behandelt nicht alle Spezialfälle von Digest Auth.
  • IP, Benutzername und Passwort anpassen.
 
Zuletzt bearbeitet:

Oneixee5

Top Contributor
Ich habe auch den subjektiven Eindruck, dass der Login ne Weile gilt auch ohne Speichern. (wäre genau zu verifizieren)
Das ist normal. Shelly-Geräte mit Web-UI können Cookies für die Web-Oberfläche nutzen, aber für die API-Zugriffe (z.B. /rpc/Shelly.GetStatus) ist das nicht üblich. Die Authentifizierung muss bei jedem API-Request erneut erfolgen.

Wenn man Session-Cookie's verwendet, muss man sich das Session-Cookie merken und bei jeder Anfrage mitgeben. -> Dann wieder das Session-Cookie aus der Antwort merken und bei der nächsten Anfrage mitsenden -> und immer so weiter. Das Merken ist erforderlich, um zu sehen wie lange das Cookie noch gültig ist. Der Inhalt (Session-ID) ändert sich normalerweise nicht. Solange die Session gültig ist muss man sich nicht neu anmelden. Eine neue Anmeldung ist dann nur erforderlich wenn man im HTTP-Status der Antwort die 401 bekommt.
 

Ähnliche Java Themen

Neue Themen


Oben