Java实现Microsoft客户端登录获取Token及Minecraft游戏验证求助
Java实现Microsoft登录获取Minecraft Token及游戏拥有验证
以下是完整的实现流程和代码示例,覆盖从Microsoft OAuth2授权到Minecraft Token获取、游戏权限验证的全流程:
核心流程概述
- 使用PKCE模式完成Microsoft OAuth2授权,获取授权码
- 通过授权码交换得到Microsoft access_token
- 用Microsoft token获取Xbox Live身份凭证
- 通过Xbox凭证获取XSTS令牌
- 用XSTS令牌兑换Minecraft服务的access_token
- 调用Minecraft官方API验证账号是否拥有游戏权限
1. 生成PKCE参数(安全授权必备)
PKCE用于桌面应用的安全授权,避免授权码被拦截:
import java.security.SecureRandom; import java.util.Base64; public class PKCEGenerator { public static String generateCodeVerifier() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } public static String generateCodeChallenge(String codeVerifier) { try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(codeVerifier.getBytes("UTF-8")); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } catch (Exception e) { throw new RuntimeException("生成Code Challenge失败", e); } } }
2. 构造Microsoft授权URL
引导用户在浏览器完成登录,获取授权码:
import java.net.URLEncoder; import java.nio.charset.StandardCharsets; public class AuthUrlBuilder { // Minecraft官方启动器使用的公开客户端ID private static final String CLIENT_ID = "00000000402b5328"; private static final String REDIRECT_URI = "https://login.live.com/oauth20_desktop.srf"; private static final String SCOPE = "XboxLive.signin offline_access"; public static String buildAuthUrl(String codeChallenge) { return String.format( "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s&code_challenge=%s&code_challenge_method=S256", CLIENT_ID, URLEncoder.encode(REDIRECT_URI, StandardCharsets.UTF_8), URLEncoder.encode(SCOPE, StandardCharsets.UTF_8), codeChallenge ); } }
3. 交换授权码获取Microsoft Token
用用户返回的授权码,结合code_verifier获取Microsoft的access_token和refresh_token:
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; import com.google.gson.Gson; public class MicrosoftTokenFetcher { private static final String CLIENT_ID = "00000000402b5328"; private static final String REDIRECT_URI = "https://login.live.com/oauth20_desktop.srf"; private static final String TOKEN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"; public static Map<String, Object> getMicrosoftToken(String code, String codeVerifier) throws Exception { HttpClient client = HttpClient.newHttpClient(); String body = String.format( "client_id=%s&code=%s&redirect_uri=%s&grant_type=authorization_code&code_verifier=%s", CLIENT_ID, code, URLEncoder.encode(REDIRECT_URI, StandardCharsets.UTF_8), codeVerifier ); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(TOKEN_URL)) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return new Gson().fromJson(response.body(), Map.class); } }
4. 获取Xbox Live身份令牌
将Microsoft token转换为Xbox Live的身份凭证:
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; import com.google.gson.Gson; public class XboxLiveTokenFetcher { private static final String XBL_AUTH_URL = "https://user.auth.xboxlive.com/user/authenticate"; public static Map<String, Object> getXboxLiveToken(String microsoftAccessToken) throws Exception { HttpClient client = HttpClient.newHttpClient(); String payload = new Gson().toJson(Map.of( "Properties", Map.of( "AuthMethod", "RPS", "SiteName", "user.auth.xboxlive.com", "RpsTicket", "d=" + microsoftAccessToken ), "RelyingParty", "http://auth.xboxlive.com", "TokenType", "JWT" )); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(XBL_AUTH_URL)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return new Gson().fromJson(response.body(), Map.class); } }
5. 获取XSTS令牌
通过Xbox Live令牌获取XSTS凭证,用于访问Minecraft服务:
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Map; import com.google.gson.Gson; public class XstsTokenFetcher { private static final String XSTS_AUTH_URL = "https://xsts.auth.xboxlive.com/xsts/authorize"; public static Map<String, Object> getXstsToken(String xboxLiveToken) throws Exception { HttpClient client = HttpClient.newHttpClient(); String payload = new Gson().toJson(Map.of( "Properties", Map.of( "SandboxId", "RETAIL", "UserTokens", new String[]{xboxLiveToken} ), "RelyingParty", "rp://api.minecraftservices.com/", "TokenType", "JWT" )); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(XSTS_AUTH_URL)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return new Gson().fromJson(response.body(), Map.class); } }
6. 获取Minecraft Token并验证游戏权限
最后兑换Minecraft服务的token,并验证账号是否拥有Java版游戏:
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; import java.util.Map; import com.google.gson.Gson; public class MinecraftAuth { private static final String MINECRAFT_TOKEN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"; private static final String MINECRAFT_OWNERSHIP_URL = "https://api.minecraftservices.com/entitlements/mcstore"; private static final String MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"; public static Map<String, Object> getMinecraftToken(String xstsToken, String uhs) throws Exception { HttpClient client = HttpClient.newHttpClient(); String payload = new Gson().toJson(Map.of( "identityToken", "XBL3.0 x=" + uhs + ";" + xstsToken )); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(MINECRAFT_TOKEN_URL)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(payload)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return new Gson().fromJson(response.body(), Map.class); } public static boolean hasMinecraftJavaEdition(String minecraftAccessToken) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(MINECRAFT_OWNERSHIP_URL)) .header("Authorization", "Bearer " + minecraftAccessToken) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); Map<String, Object> entitlements = new Gson().fromJson(response.body(), Map.class); List<Map<String, Object>> items = (List<Map<String, Object>>) entitlements.get("items"); for (Map<String, Object> item : items) { if ("game_minecraft".equals(item.get("name"))) { return true; } } return false; } public static Map<String, Object> getMinecraftProfile(String minecraftAccessToken) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(MINECRAFT_PROFILE_URL)) .header("Authorization", "Bearer " + minecraftAccessToken) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return new Gson().fromJson(response.body(), Map.class); } }
完整流程调用示例
import java.util.Map; import java.util.Scanner; public class Main { public static void main(String[] args) throws Exception { // 1. 生成PKCE参数 String codeVerifier = PKCEGenerator.generateCodeVerifier(); String codeChallenge = PKCEGenerator.generateCodeChallenge(codeVerifier); // 2. 输出授权URL,引导用户登录 String authUrl = AuthUrlBuilder.buildAuthUrl(codeChallenge); System.out.println("请在浏览器打开以下链接登录Microsoft账号:"); System.out.println(authUrl); System.out.println("登录完成后,复制URL中的`code`参数并输入:"); Scanner scanner = new Scanner(System.in); String authCode = scanner.nextLine().trim(); // 3. 获取Microsoft Token Map<String, Object> microsoftToken = MicrosoftTokenFetcher.getMicrosoftToken(authCode, codeVerifier); String msAccessToken = (String) microsoftToken.get("access_token"); // 4. 获取Xbox Live Token Map<String, Object> xblToken = XboxLiveTokenFetcher.getXboxLiveToken(msAccessToken); String xblTokenValue = (String) xblToken.get("Token"); // 解析UHS(用户唯一标识) String uhs = ((Map<String, Object>) ((List<?>) ((Map<String, Object>) xblToken.get("DisplayClaims")).get("xui")).get(0)).get("uhs").toString(); // 5. 获取XSTS Token Map<String, Object> xstsToken = XstsTokenFetcher.getXstsToken(xblTokenValue); String xstsTokenValue = (String) xstsToken.get("Token"); // 6. 获取Minecraft Token Map<String, Object> mcToken = MinecraftAuth.getMinecraftToken(xstsTokenValue, uhs); String mcAccessToken = (String) mcToken.get("access_token"); System.out.println("\nMinecraft Access Token: " + mcAccessToken); // 7. 验证游戏拥有状态 boolean hasJavaEdition = MinecraftAuth.hasMinecraftJavaEdition(mcAccessToken); System.out.println("是否拥有Minecraft Java版:" + (hasJavaEdition ? "是" : "否")); // 8. 获取玩家信息 Map<String, Object> profile = MinecraftAuth.getMinecraftProfile(mcAccessToken); System.out.println("玩家ID:" + profile.get("id")); System.out.println("玩家名称:" + profile.get("name")); } }
注意事项
- 依赖引入:需要添加Gson库处理JSON解析(Maven依赖:
com.google.code.gson:gson:2.10.1) - 异常处理:实际应用中需完善网络异常、授权失败等场景的捕获与处理
- Token刷新:当Minecraft access_token过期时,可使用Microsoft的refresh_token重新获取,流程类似步骤3,只需将
grant_type改为refresh_token - 权限范围:确保授权时的
scope包含XboxLive.signin和offline_access,前者用于Xbox授权,后者用于获取refresh_token实现持久登录
内容的提问来源于stack exchange,提问作者secRaphyTwin Go
相关产品推荐
相关产品推荐

