[MSA] Authentication implemented

TODO:
- Use Json format to save account
- [MSA] Refresh implementation
This commit is contained in:
khanhduytran0 2020-12-10 14:36:57 +07:00
parent 6a2aaaab70
commit 6c250d32c2
6 changed files with 154 additions and 27 deletions

View File

@ -1,7 +1,9 @@
package com.kdt.mojangauth; package com.kdt.mojangauth;
import net.kdt.pojavlaunch.*;
public interface RefreshListener public interface RefreshListener
{ {
public void onFailed(Throwable e); public void onFailed(Throwable e);
public void onSuccess(); public void onSuccess(MCProfile.Builder profile);
} }

View File

@ -65,7 +65,7 @@ public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
public void onPostExecute(Throwable result) { public void onPostExecute(Throwable result) {
build.dismiss(); build.dismiss();
if (result == null) { if (result == null) {
listener.onSuccess(); listener.onSuccess(null);
} else { } else {
listener.onFailed(result); listener.onFailed(result);
} }

View File

@ -327,8 +327,9 @@ public class PojavLoginActivity extends BaseActivity
} }
@Override @Override
public void onSuccess() { public void onSuccess(MCProfile.Builder b) {
// TODO: Implement this method mProfile = b;
playProfile();
} }
}).execute(code); }).execute(code);
// Toast.makeText(this, "Logged in to Microsoft account, but NYI", Toast.LENGTH_LONG).show(); // Toast.makeText(this, "Logged in to Microsoft account, but NYI", Toast.LENGTH_LONG).show();
@ -733,7 +734,7 @@ public class PojavLoginActivity extends BaseActivity
} }
@Override @Override
public void onSuccess() public void onSuccess(MCProfile.Builder unused)
{ {
MCProfile.launch(PojavLoginActivity.this, path); MCProfile.launch(PojavLoginActivity.this, path);
} }

View File

@ -27,7 +27,11 @@ public class HttpResponse<T>
public T body() throws IOException { public T body() throws IOException {
Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
if (type.isAssignableFrom(String.class)) { if (type.isAssignableFrom(String.class)) {
return (T) Tools.read(mRequest.mBuilder.getBase().getInputStream()); if (statusCode() >= 200 && statusCode() < 300) {
return (T) Tools.read(mRequest.mBuilder.getBase().getInputStream());
} else {
return (T) Tools.read(mRequest.mBuilder.getBase().getErrorStream());
}
} else { } else {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -15,13 +15,15 @@ import org.json.*;
import java.text.ParseException; import java.text.ParseException;
import java.io.*; import java.io.*;
import net.kdt.pojavlaunch.value.launcherprofiles.*;
import net.kdt.pojavlaunch.value.*;
public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> { public class MicrosoftAuthenticator extends AsyncTask<String, Void, Object> {
private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf"; private static final String authTokenUrl = "https://login.live.com/oauth20_token.srf";
private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate"; private static final String xblAuthUrl = "https://user.auth.xboxlive.com/user/authenticate";
private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize"; private static final String xstsAuthUrl = "https://xsts.auth.xboxlive.com/xsts/authorize";
private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox"; private static final String mcLoginUrl = "https://api.minecraftservices.com/authentication/login_with_xbox";
private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore"; // private static final String mcStoreUrl = "https://api.minecraftservices.com/entitlements/mcstore";
private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile"; private static final String mcProfileUrl = "https://api.minecraftservices.com/minecraft/profile";
//private Gson gson = new Gson(); //private Gson gson = new Gson();
@ -45,9 +47,8 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
} }
@Override @Override
public Throwable doInBackground(String... args) { public Object doInBackground(String... args) {
try { try {
MCProfile.Builder profilePath = MCProfile.load(args[0]);
String authCode = args[1]; String authCode = args[1];
publishProgress(); publishProgress();
@ -57,16 +58,24 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
String xblToken = acquireXBLToken(msaAccessToken); String xblToken = acquireXBLToken(msaAccessToken);
publishProgress(); publishProgress();
// TODO String[] xstsData = acquireXsts(xblToken);
/* publishProgress();
profilePath.setClientID(response.clientToken.toString()); String mcAccessToken = acquireMinecraftToken(xstsData[0], xstsData[1]);
profilePath.setAccessToken(response.accessToken);
profilePath.setUsername(response.selectedProfile.name); publishProgress();
profilePath.setProfileID(response.selectedProfile.id); // TODO migrate account format to json
MinecraftAccount acc = checkMcProfile(mcAccessToken);
MCProfile.Builder profilePath = MCProfile.load(args[0]);
profilePath.setClientID("0" /* FIXME */);
profilePath.setAccessToken(acc.accessToken);
profilePath.setUsername(acc.username);
profilePath.setProfileID(acc.profileId);
MCProfile.build(profilePath); MCProfile.build(profilePath);
*/
return null; return profilePath;
} catch (Throwable e) { } catch (Throwable e) {
return e; return e;
} }
@ -76,16 +85,15 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
protected void onProgressUpdate(Void[] p1) { protected void onProgressUpdate(Void[] p1) {
super.onProgressUpdate(p1); super.onProgressUpdate(p1);
build.setProgress(build.getProgress() + 1); build.setProgress(build.getProgress() + 1);
} }
@Override @Override
public void onPostExecute(Throwable result) { public void onPostExecute(Object result) {
build.dismiss(); build.dismiss();
if (result == null) { if (result instanceof MCProfile.Builder) {
listener.onSuccess(); listener.onSuccess((MCProfile.Builder) result);
} else { } else {
listener.onFailed(result); listener.onFailed((Throwable) result);
} }
} }
@ -115,12 +123,27 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
private final class XSTSXUI {
private String uhs;
}
private final class XSTSDisplayClaims {
private XSTSXUI[] xui;
}
private final class GlobalToken { private final class GlobalToken {
// MSA AccessToken // MSA AccessToken
private String access_token; private String access_token;
// XBL Token // XBL, XSTS Token
private String Token; private String Token;
// XSTS
private XSTSDisplayClaims DisplayClaims;
// Minecraft side
private String id;
private String name;
} }
private String acquireAccessToken(String authcode) throws IOException, URISyntaxException{ private String acquireAccessToken(String authcode) throws IOException, URISyntaxException{
@ -137,8 +160,12 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
.header("Content-Type", "application/x-www-form-urlencoded") .header("Content-Type", "application/x-www-form-urlencoded")
.POST(ofFormData(data)).build(); .POST(ofFormData(data)).build();
HttpResponse response = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString()); HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
return Tools.GLOBAL_GSON.fromJson((String) response.body(), GlobalToken.class).access_token; if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).access_token;
} else {
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
}
} }
private String acquireXBLToken(String accessToken) throws IOException, URISyntaxException { private String acquireXBLToken(String accessToken) throws IOException, URISyntaxException {
@ -160,7 +187,89 @@ public class MicrosoftAuthenticator extends AsyncTask<String, Void, Throwable> {
.POST(ofJSONData(data)).build(); .POST(ofJSONData(data)).build();
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString()); HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).Token; if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
return Tools.GLOBAL_GSON.fromJson((String) resp.body(), GlobalToken.class).Token;
} else {
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
}
}
private String[] acquireXsts(String xblToken) throws IOException, URISyntaxException {
URI uri = new URI(xstsAuthUrl);
Map<Object, Object> dataProp = new ArrayMap<>();
dataProp.put("SandboxId", "RETAIL");
dataProp.put("UserTokens", Arrays.asList(xblToken));
Map<Object, Object> data = new ArrayMap<>();
data.put("Properties", dataProp);
data.put("RelyingParty", "rp://api.minecraftservices.com/");
data.put("TokenType", "JWT");
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(ofJSONData(data)).build();
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
String body = (String) resp.body();
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
String xblXsts = token.Token;
String uhs = token.DisplayClaims.xui[0].uhs;
return new String[]{uhs, xblXsts};
} else {
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
}
}
private String acquireMinecraftToken(String xblUhs, String xblXsts) throws IOException, URISyntaxException {
URI uri = new URI(mcLoginUrl);
Map<Object, Object> data = new ArrayMap<>();
data.put("identityToken", "XBL3.0 x=" + xblUhs + ";" + xblXsts);
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(ofJSONData(data)).build();
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
String body = (String) resp.body();
return Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class).access_token;
} else {
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
}
}
private MinecraftAccount checkMcProfile(String mcAccessToken) throws IOException, URISyntaxException {
URI uri = new URI(mcProfileUrl);
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Authorization", "Bearer " + mcAccessToken)
.GET().build();
HttpResponse resp = HttpClient.newBuilder().build().sendRequest(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
String body = (String) resp.body();
GlobalToken token = Tools.GLOBAL_GSON.fromJson(body, GlobalToken.class);
String uuid = token.id;
String uuidDashes = uuid.replaceFirst(
"(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"
);
MinecraftAccount acc = new MinecraftAccount();
acc.isMicrosoft = true;
acc.username = token.name;
acc.accessToken = mcAccessToken;
acc.profileId = uuidDashes;
return acc;
} else {
throw new RuntimeException("Error " + resp.statusCode() + ": " + (String) resp.body());
}
} }
public static HttpRequest.BodyPublisher ofJSONData(Map<Object, Object> data) { public static HttpRequest.BodyPublisher ofJSONData(Map<Object, Object> data) {

View File

@ -0,0 +1,11 @@
package net.kdt.pojavlaunch.value;
public class MinecraftAccount
{
public String accessToken; // access token
public String clientToken; // clientID: refresh and invalidate
public String profileId; // authenticate UUID
public String username;
public String selectedVersion = "1.7.10";
public boolean isMicrosoft;
}