ITADN

Authorization request to keymaster returns 403

#1099Closedltcmdrkeen 创建于 2025-08-13
bug
L
ltcmdrkeencommented
**Describe the bug** 403 Status code is returned from a Mercury request when requesting token, see - https://github.com/librespot-org/librespot/issues/1532 and similar bugs in other repositories: - https://github.com/aome510/spotify-player/issues/802 **Screenshots/Stracktraces/Logs** <img width="2016" height="646" alt="Image" src="https://github.com/user-attachments/assets/1d3ecd60-9c0b-4e57-9983-65fa633c1186" /> **Version/Commit** 1.6.6 **Patch** As @photovoltex suggested in [librespot issue 1532](https://github.com/librespot-org/librespot/issues/1532), I've implemented a change to use Login5: - the login requires a b64 credentials string which we can still issue using AuthStrategy "OAUTH" - librespot will then create the `credentials.json` which we use in the AuthStrategy "STORED" replacing mercury with Login5 - I have added a new helper class that generates the token using Login5 and changed the TokenProvider to use that class ... Although it is not a complete solution, it works for now. This is the patch [Replaced_getting_token_via_mercury_with_Login5.patch](https://github.com/user-attachments/files/21749232/Replaced_getting_token_via_mercury_with_Login5.patch) ```java Subject: [PATCH] Replaced getting token via mercury with Login5 --- Index: lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java b/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java new file mode 100644 --- /dev/null (revision bf18fb6689373c435bf94fa978770accf222cddd) +++ b/lib/src/main/java/xyz/gianlu/librespot/core/Login5TokenHelper.java (revision bf18fb6689373c435bf94fa978770accf222cddd) @@ -0,0 +1,82 @@ +package xyz.gianlu.librespot.core; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.protobuf.ByteString; +import com.spotify.login5v3.Credentials; +import com.spotify.login5v3.Login5; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Base64; + +public final class Login5TokenHelper { + private Login5TokenHelper() {} + + /** Parse credentials.json and build the login5 StoredCredential. */ + private static Credentials.StoredCredential readStoredCredential(File storedCredentialsFile) throws IOException { + try (FileReader reader = new FileReader(storedCredentialsFile)) { + JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject(); + String username = obj.get("username").getAsString(); + String base64Creds = obj.get("credentials").getAsString(); + byte[] data = Base64.getDecoder().decode(base64Creds); + + return Credentials.StoredCredential.newBuilder() + .setUsername(username) + .setData(ByteString.copyFrom(data)) + .build(); + } + } + + /** + * Fetch a StoredToken via login5 using an absolute path to the credentials.json file. + * + * Requires a public static factory in TokenProvider: + * + * public static StoredToken fromJsonObject(com.google.gson.JsonObject obj) { + * return new StoredToken(obj); + * } + */ + public static TokenProvider.StoredToken fetchStoredToken(Session session, + Path storedCredentialsPath, + String... scopes) throws IOException { + File storedFile = storedCredentialsPath.toFile(); + if (!storedFile.isFile()) { + throw new IOException("Credentials file not found: " + storedFile.getAbsolutePath()); + } + + Credentials.StoredCredential sc = readStoredCredential(storedFile); + + String accessToken; + int expiresIn; + + try { + Login5Api api = new Login5Api(session); + Login5.LoginResponse resp = api.login5( + Login5.LoginRequest.newBuilder() + .setStoredCredential(sc) + .build()); + + if (!resp.hasOk()) throw new IOException("login5 did not return OK: " + resp); + accessToken = resp.getOk().getAccessToken(); + expiresIn = resp.getOk().getAccessTokenExpiresIn(); + } catch (Exception e) { + throw new IOException("login5 request failed", e); + } + + // Build JSON that StoredToken expects: scope must be an array + JsonArray scopeArr = new JsonArray(); + for (String s : scopes) scopeArr.add(s); + + JsonObject obj = new JsonObject(); + obj.addProperty("accessToken", accessToken); + obj.addProperty("expiresIn", expiresIn); + obj.addProperty("tokenType", "Bearer"); + obj.add("scope", scopeArr); + + return TokenProvider.fromJsonObject(obj); // Requires factory method in TokenProvider + } +} Index: lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java b/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java --- a/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java (revision 1437b1581efa92c08f9215f0be930237f5e6cda6) +++ b/lib/src/main/java/xyz/gianlu/librespot/core/TokenProvider.java (revision bf18fb6689373c435bf94fa978770accf222cddd) @@ -23,9 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xyz.gianlu.librespot.common.Utils; -import xyz.gianlu.librespot.json.GenericJson; import xyz.gianlu.librespot.mercury.MercuryClient; -import xyz.gianlu.librespot.mercury.MercuryRequests; import java.io.IOException; import java.util.ArrayList; @@ -55,6 +53,7 @@ return null; } + @NotNull public synchronized StoredToken getToken(@NotNull String... scopes) throws IOException, MercuryClient.MercuryException { if (scopes.length == 0) throw new IllegalArgumentException(); @@ -66,8 +65,17 @@ } LOGGER.debug("Token expired or not suitable, requesting again. {scopes: {}, oldToken: {}}", Arrays.asList(scopes), token); - GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes))); - token = new StoredToken(resp.obj); + + + //GenericJson resp = session.mercury().sendSync(MercuryRequests.requestToken(session.deviceId(), String.join(",", scopes))); + //token = new StoredToken(obj); + Session.Configuration conf = session.configuration(); + + try { + token = Login5TokenHelper.fetchStoredToken(session, java.nio.file.Paths.get(conf.storedCredentialsFile.getAbsolutePath()), scopes); + } catch (IOException e) { + throw new IOException("Failed to get token via login5", e); + } LOGGER.debug("Updated token successfully! {scopes: {}, newToken: {}}", Arrays.asList(scopes), token); tokens.add(token); @@ -80,6 +88,10 @@ return getToken(scope).accessToken; } + public static StoredToken fromJsonObject(com.google.gson.JsonObject obj) { + return new StoredToken(obj); + } + public static class StoredToken { public final int expiresIn; public final String accessToken; ```
关闭于 2025-08-14 4 条评论