大家好这里是皇鱼
在这篇文章中,我将教会各位如何进行微软登录
本文以C#为例,那么开始
事先声明:本文以微软最新的OAuth2.0授权代码流为参考,请先在MS Entra中注册一个应用程序,接下来按照Mojang的要求填写申请表,通过审核后方可进行本文操作,否则在请求Mojang api的时候无法正常返回
微软登录
获取授权码
首先在你的Azure应用程序中找到重定向URI这一项
点进去,添加一个本地链接,比如127.0.0.1:40935
接下来你需要让用户访问如下url并在本地搭建一个临时网站服务器,端口是上面设置的端口:
https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
?client_id=
&response_type=code
&redirect_uri=http://127.0.0.1:40935
&response_mode=query
&scope=XboxLive.signin offline_access
此处client_id
填写你Azure应用程序的client_id,redirect_uri
填写上面添加的本地连接,用户在浏览器登录完成后会跳转到
上面的回调链接/?code=xxx
你需要通过临时网站服务器提取这个code,这是授权码
C#代码示例:
public static void stepOne()
{
Console.WriteLine("MSL step 1");
string url = "http://127.0.0.1:40935/";
string loginurl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=1cbfda79-fc84-47f9-8110-f924da9841ec&response_type=code&redirect_uri=http://127.0.0.1:40935&response_mode=query&scope=XboxLive.signin+offline_access";
System.Diagnostics.Process.Start("explorer.exe", $"\"{loginurl}\"");
HttpListener listener = new HttpListener();
listener.Prefixes.Add(url);
listener.Start();
Console.WriteLine("Listening...");
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
if (request.QueryString["code"] != null)
{
string code = request.QueryString["code"];
string responseString = $"<html><body><center><h1>您已登录到Line Launcher,现在可以关闭此页面。</h1></center></body></html>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.ContentType = "text/html; charset=UTF-8";
response.ContentEncoding = Encoding.UTF8;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
listener.Stop();
Console.WriteLine("Server stopped.");
Console.WriteLine("MSL step 2");
stepTwo(code);
}
else
{
string responseString = "<html><body><center><h1>登录失败,请重试!</h1><center></body></html>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.ContentType = "text/html; charset=UTF-8";
response.ContentEncoding = Encoding.UTF8;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
授权码到授权令牌
首先你需要对https://login.microsoftonline.com/consumers/oauth2/v2.0/token
发起post请求,如下是发送的数据:
var params = new Dictionary<string, string>
{
{ "client_id", "Azure client_id" },
{ "scope", "XboxLive.signin offline_access" },
{ "code", "上一步的code"},
{ "redirect_uri", "按照官方文档是必填,且需和上一步相同,但实际无作用"},
{ "grant_type", "authorization_code"}
};
记得设置ContentType
为application/x-www-form-urlencoded
,Accept
为application/json
如下是返回:
{
"token_type":"Bearer",
"scope":"XboxLive.signin XboxLive.offline_access",
"expires_in":3600,
"ext_expires_in":3600,
"access_token":"<令牌>",
"refresh_token":"<刷新令牌>"
}
你需要其中的access_token
用于登录,refresh_token
用于刷新access_token
,因为access_token
是有使用期限的
C#示例:
public static void stepTwo(string code)
{
string url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
var parameters = new Dictionary<string, string>
{
{ "client_id", "" },
{ "scope", "XboxLive.signin offline_access" },
{ "code", code},
{ "redirect_uri", "http://127.0.0.1:40935"},
{ "grant_type", "authorization_code"}
};
string context = PostWithParameters(parameters, url, "application/json", "application/x-www-form-urlencoded");
string accesstoken = GetValueFromJson(context, "access_token");
string refreshtoken = GetValueFromJson(context, "refresh_token");
Console.WriteLine($"MSL step 3");
stepThree(accesstoken);
}
刷新access_token
POST https://login.microsoftonline.com/consumers/oauth2/v2.0/token,参数:
var params = new Dictionary<string, string>
{
{ "client_id", "Azure应用程序的client_id"},
{ "scope", "XboxLive.signin offline_access" },
{ "refresh_token", 上一步的refresh_token},
{ "grant_type", "refresh_token"}
};
返回和上一步相同
XBL身份验证
POST https://login.microsoftonline.com/consumers/oauth2/v2.0/token
data:
{
"Properties": {
"AuthMethod": "RPS",
"SiteName": "user.auth.xboxlive.com",
"RpsTicket": "d=token" // token=刚刚的accesstoken,bad request可以删掉d=
},
"RelyingParty": "http://auth.xboxlive.com",
"TokenType": "JWT"
}
记得ContentType
和Accept
设置为application/json
返回:
{
"IssueInstant":"2020-12-07T19:52:08.4463796Z",
"NotAfter":"2020-12-21T19:52:08.4463796Z",
"Token":"token", // XBL令牌
"DisplayClaims":{
"xui":[
{
"uhs":"uhs" //用户哈希值
}
]
}
}
你需要xbl令牌和用户哈希值(user hash uhs)以进行下一步
XSTS身份验证
POST https://xsts.auth.xboxlive.com/xsts/authorize
data:
{
"Properties": {
"SandboxId": "RETAIL",
"UserTokens": [
"xbl" // 上面的XBL令牌
]
},
"RelyingParty": "rp://api.minecraftservices.com/",
"TokenType": "JWT"
}
返回:
{
"IssueInstant":"2020-12-07T19:52:09.2345095Z",
"NotAfter":"2020-12-08T11:52:09.2345095Z",
"Token":"token", // xsts令牌
"DisplayClaims":{
"xui":[
{
"uhs":"" // 相同
}
]
}
}
你需要xsts令牌以进行下一步的操作
MC访问令牌
POST https://api.minecraftservices.com/authentication/login_with_xbox
{
"identityToken": "XBL3.0 x=<uhs>;<xsts_token>"
}
返回:
{
"username" : "没用",
"roles" : [ ],
"access_token" : "mc access token", // mc令牌
"token_type" : "Bearer",
"expires_in" : 86400
}
检查是否购买游戏
全是枯燥的HTTP请求,如下转自mc wiki(https://zh.minecraft.wiki/w/Tutorial:编写启动器):
那么现在让我们使用Minecraft的访问令牌来检查该账号是否包含产品许可。
GET https://api.minecraftservices.com/entitlements/mcstore
访问令牌在验证文件头中:Authorization: Bearer token。你需要保留Bearer,并在Bearer后添加上一步的访问令牌。如果用户拥有游戏,那么响应会看起来像这样:
{
"items" : [ {
"name" : "product_minecraft",
"signature" : "jwt sig"
}, {
"name" : "game_minecraft",
"signature" : "jwt sig"
} ],
"signature" : "jwt sig",
"keyId" : "1"
}
第一个jwts会包含值:
{
"typ": "JWT",
"alg": "RS256",
"kid": "1"
}.{
"signerId": "2535416586892404",
"name": "product_minecraft"
}.[Signature]
最后一个jwt看起来是这样解码的:
{
"typ": "JWT",
"alg": "RS256",
"kid": "1"
}.{
"entitlements": [
{
"name": "product_minecraft"
},
{
"name": "game_minecraft"
}
],
"signerId": "2535416586892404"
}.[Signature]
如果该账号没有拥有游戏,那么项目为空。
获取玩家 UUID
启动游戏还需要玩家的UUID,我们目前也没有获得。不过只要玩家拥有游戏,就一定有办法获取其UUID:
现在我们知道了该账号拥有游戏,那么可以获取他的档案来得到UUID:
GET https://api.minecraftservices.com/minecraft/profile
同样,访问令牌在验证文件头中:Authorization: Bearer token
如果账号拥有游戏,响应看起来会像这样:
{
"id" : "986dec87b7ec47ff89ff033fdb95c4b5", // 账号的真实UUID
"name" : "HowDoesAuthWork", // 该账号的Minecraft用户名
"skins" : [ {
"id" : "6a6e65e5-76dd-4c3c-a625-162924514568",
"state" : "ACTIVE",
"url" : "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b",
"variant" : "CLASSIC",
"alias" : "STEVE"
} ],
"capes" : [ ]
}
否则会看起来像这样:
{
"path" : "/minecraft/profile",
"errorType" : "NOT_FOUND",
"error" : "NOT_FOUND",
"errorMessage" : "The server has not found anything matching the request URI",
"developerMessage" : "The server has not found anything matching the request URI"
}
完整C#示例代码:
public static void stepOne()
{
Console.WriteLine("MSL step 1");
string url = "http://127.0.0.1:40935/";
string loginurl = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=&response_type=code&redirect_uri=http://127.0.0.1:40935&response_mode=query&scope=XboxLive.signin+offline_access";
System.Diagnostics.Process.Start("explorer.exe", $"\"{loginurl}\"");
HttpListener listener = new HttpListener();
listener.Prefixes.Add(url);
listener.Start();
Console.WriteLine("Listening...");
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
if (request.QueryString["code"] != null)
{
string code = request.QueryString["code"];
string responseString = $"<html><body><center><h1>您已登录,现在可以关闭此页面。</h1></center></body></html>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.ContentType = "text/html; charset=UTF-8";
response.ContentEncoding = Encoding.UTF8;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
listener.Stop();
Console.WriteLine("Server stopped.");
Console.WriteLine("MSL step 2");
stepTwo(code);
}
else
{
string responseString = "<html><body><center><h1>登录失败,请重试!</h1><center></body></html>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.ContentType = "text/html; charset=UTF-8";
response.ContentEncoding = Encoding.UTF8;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
public static void stepTwo(string code)
{
string url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
var parameters = new Dictionary<string, string>
{
{ "client_id", "1c" },
{ "scope", "XboxLive.signin offline_access" },
{ "code", code},
{ "redirect_uri", "http://127.0.0.1:40935"},
{ "grant_type", "authorization_code"}
};
string context = PostWithParameters(parameters, url, "application/json", "application/x-www-form-urlencoded");
string accesstoken = GetValueFromJson(context, "access_token");
string refreshtoken = GetValueFromJson(context, "refresh_token");
Console.WriteLine($"MSL step 3");
stepThree(accesstoken);
Console.WriteLine("MSL refreshtoken test");
rttest(refreshtoken);
}
public static void rttest(string rtoken)
{
string url = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
var parameters = new Dictionary<string, string>
{
{ "client_id", "1" },
{ "scope", "XboxLive.signin offline_access" },
{ "refresh_token", rtoken},
{ "grant_type", "refresh_token"}
};
string context = PostWithParameters(parameters, url, "application/json", "application/x-www-form-urlencoded");
Console.WriteLine(context);
}
public static void stepThree(string token)
{
string json = "{" +
"\"Properties\": {" +
"\"AuthMethod\": \"RPS\"," +
"\"SiteName\": \"user.auth.xboxlive.com\"," +
"\"RpsTicket\": \"d=" + token + "\"}," +
"\"RelyingParty\": \"http://auth.xboxlive.com\"," +
"\"TokenType\": \"JWT\"}";
string contenttype = "application/json";
string url = "https://user.auth.xboxlive.com/user/authenticate";
string xblres = PostWithJson(json, url, contenttype, contenttype);
string t = GetValueFromJson(xblres, "Token");
Console.WriteLine("MSL step 4");
stepFour(t);
}
public static void stepFour(string tokenth){
string json = "{" +
"\"Properties\": {" +
"\"SandboxId\": \"RETAIL\"," +
"\"UserTokens\": [\"" + tokenth + "\"]}," +
"\"RelyingParty\": \"rp://api.minecraftservices.com/\"," +
"\"TokenType\": \"JWT\"}";
string url = "https://xsts.auth.xboxlive.com/xsts/authorize";
string contenttype = "application/json";
string xstsres = PostWithJson(json,url,contenttype,contenttype);
string token = GetValueFromJson(xstsres, "Token");
string uhs = GetValueFromJson(xstsres, "DisplayClaims.xui[0].uhs");
Console.WriteLine("MSL step 5");
stepFive(token, uhs);
}
public static void stepFive(string tokenf, string uhs) {
string json = "{ \"identityToken\": \"XBL3.0 x=" + uhs + $";{tokenf}\"" + "}";
string url = "https://api.minecraftservices.com/authentication/login_with_xbox";
string contenttype = "application/json";
string mjapires = PostWithJson(json,url, contenttype, contenttype);
string token = GetValueFromJson(mjapires, "access_token");
Console.WriteLine("MSL step 6");
stepSix(token);
}
public static void stepSix(string tokenf)
{
string url = "https://api.minecraftservices.com/entitlements/mcstore";
string accept = "application/json";
string checkres = GetWithAuth($"Bearer {tokenf}", url, accept);
var jsonObject = JObject.Parse(checkres);
var items = jsonObject.SelectToken("items") as JArray;
url = "https://api.minecraftservices.com/minecraft/profile";
string profileres = GetWithAuth($"Bearer {tokenf}", url, accept);
bool haveMc = !(items == null || items.Count == 0 || profileres.Contains("NOT_FOUND"));
Console.WriteLine("Does Minecraft have : " + haveMc.ToString());
if (haveMc)
{
string uuid = GetValueFromJson(profileres, "id");
string name = GetValueFromJson(profileres, "name");
Console.WriteLine($"uuid={uuid}\nname={name}");
}
}
参考资料
Minecraft Wiki:https://zh.minecraft.wiki
Wiki.vg:https://wiki.vg/
最后修改:2024-08-06 13:09
本文链接:https://blog.huangyu.win/index.php/archives/29/
版权声明:本文 如何编写一个Minecraft Java版启动器 | Part 2 微软登录 | 2-2 为 皇鱼 原创。著作权归作者所有,如无特殊声明,本文将依据CC BY-NC-SA 3.0 CN发布,请注意版权。
转载说明:请依据CC BY-NC-SA 3.0 CN进行转载。
2 条评论
补:
接下来是最后一篇3-1,讲其他一些相关内容,比如皮肤修改等
好的不是最后一篇