您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
209 行
8.3 KiB
209 行
8.3 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Unity.Services.Authentication.Models;
|
|
using Unity.Services.Authentication.Utilities;
|
|
|
|
namespace Unity.Services.Authentication
|
|
{
|
|
interface IAuthenticationNetworkClient
|
|
{
|
|
IWebRequest<WellKnownKeys> GetWellKnownKeys();
|
|
IWebRequest<SignInResponse> SignInAnonymously();
|
|
IWebRequest<SignInResponse> SignInWithSessionToken(string token);
|
|
IWebRequest<SignInResponse> SignInWithExternalToken(ExternalTokenRequest externalToken);
|
|
IWebRequest<SignInResponse> LinkWithExternalToken(string accessToken, ExternalTokenRequest externalToken);
|
|
}
|
|
|
|
class AuthenticationNetworkClient : IAuthenticationNetworkClient
|
|
{
|
|
const string k_WellKnownUrlStem = "/.well-known/jwks.json";
|
|
const string k_AnonymousUrlStem = "/authentication/anonymous";
|
|
const string k_SessionTokenUrlStem = "/authentication/session-token";
|
|
const string k_ExternalTokenUrlStem = "/authentication/external-token";
|
|
const string k_LinkExternalTokenUrlStem = "/authentication/link";
|
|
const string k_OAuthUrlStem = "/oauth2/auth";
|
|
const string k_OAuthTokenUrlStem = "/oauth2/token";
|
|
const string k_OAuthScope = "openid offline unity.user identity.user";
|
|
const string k_AuthResponseType = "code";
|
|
const string k_ChallengeMethod = "S256";
|
|
const string k_OauthRevokeStem = "/oauth2/revoke";
|
|
|
|
readonly INetworkingUtilities m_NetworkClient;
|
|
readonly ICodeChallengeGenerator m_CodeChallengeGenerator;
|
|
readonly ILogger m_Logger;
|
|
|
|
readonly string m_WellKnownUrl;
|
|
readonly string m_AnonymousUrl;
|
|
readonly string m_SessionTokenUrl;
|
|
readonly string m_ExternalTokenUrl;
|
|
readonly string m_LinkExternalTokenUrl;
|
|
readonly string m_OAuthUrl;
|
|
readonly string m_OAuthTokenUrl;
|
|
readonly string m_OAuthRevokeTokenUrl;
|
|
|
|
readonly Dictionary<string, string> m_CommonHeaders;
|
|
|
|
string m_OAuthClientId;
|
|
string m_SessionChallengeCode;
|
|
|
|
internal AuthenticationNetworkClient(string authenticationHost,
|
|
string projectId,
|
|
ICodeChallengeGenerator codeChallengeGenerator,
|
|
INetworkingUtilities networkClient,
|
|
ILogger logger)
|
|
{
|
|
m_NetworkClient = networkClient;
|
|
m_CodeChallengeGenerator = codeChallengeGenerator;
|
|
m_Logger = logger;
|
|
|
|
m_OAuthClientId = "default";
|
|
|
|
m_WellKnownUrl = authenticationHost + k_WellKnownUrlStem;
|
|
m_AnonymousUrl = authenticationHost + k_AnonymousUrlStem;
|
|
m_SessionTokenUrl = authenticationHost + k_SessionTokenUrlStem;
|
|
m_ExternalTokenUrl = authenticationHost + k_ExternalTokenUrlStem;
|
|
m_LinkExternalTokenUrl = authenticationHost + k_LinkExternalTokenUrlStem;
|
|
m_OAuthUrl = authenticationHost + k_OAuthUrlStem;
|
|
m_OAuthTokenUrl = authenticationHost + k_OAuthTokenUrlStem;
|
|
m_OAuthRevokeTokenUrl = authenticationHost + k_OauthRevokeStem;
|
|
|
|
m_CommonHeaders = new Dictionary<string, string>
|
|
{
|
|
["ProjectId"] = projectId,
|
|
// The Error-Version header enables RFC7807HttpError error responses
|
|
["Error-Version"] = "v1"
|
|
};
|
|
}
|
|
|
|
public IWebRequest<WellKnownKeys> GetWellKnownKeys()
|
|
{
|
|
return m_NetworkClient.Get<WellKnownKeys>(m_WellKnownUrl);
|
|
}
|
|
|
|
public void SetOAuthClient(string oAuthClientId)
|
|
{
|
|
m_OAuthClientId = oAuthClientId;
|
|
}
|
|
|
|
public IWebRequest<SignInResponse> SignInAnonymously()
|
|
{
|
|
return m_NetworkClient.Post<SignInResponse>(m_AnonymousUrl, m_CommonHeaders);
|
|
}
|
|
|
|
public IWebRequest<SignInResponse> SignInWithSessionToken(string token)
|
|
{
|
|
return m_NetworkClient.PostJson<SignInResponse>(m_SessionTokenUrl, new SessionTokenRequest
|
|
{
|
|
SessionToken = token
|
|
}, m_CommonHeaders);
|
|
}
|
|
|
|
public IWebRequest<SignInResponse> SignInWithExternalToken(ExternalTokenRequest externalToken)
|
|
{
|
|
return m_NetworkClient.PostJson<SignInResponse>(m_ExternalTokenUrl, externalToken, m_CommonHeaders);
|
|
}
|
|
|
|
public IWebRequest<SignInResponse> LinkWithExternalToken(string accessToken, ExternalTokenRequest externalToken)
|
|
{
|
|
return m_NetworkClient.PostJson<SignInResponse>(m_LinkExternalTokenUrl, externalToken, WithAccessToken(accessToken));
|
|
}
|
|
|
|
public IWebRequest<OAuthAuthCodeResponse> RequestAuthCode(string idToken)
|
|
{
|
|
m_SessionChallengeCode = m_CodeChallengeGenerator.GenerateCode();
|
|
|
|
var payload = $"client_id={m_OAuthClientId}&" +
|
|
$"response_type={k_AuthResponseType}&" +
|
|
$"id_token={idToken}&" +
|
|
$"state={m_CodeChallengeGenerator.GenerateStateString()}&" +
|
|
$"scope={k_OAuthScope}&" +
|
|
$"code_challenge={S256EncodeChallenge(m_SessionChallengeCode)}&" +
|
|
$"code_challenge_method={k_ChallengeMethod}";
|
|
|
|
return m_NetworkClient.PostForm<OAuthAuthCodeResponse>(m_OAuthUrl, payload, m_CommonHeaders);
|
|
}
|
|
|
|
string S256EncodeChallenge(string code)
|
|
{
|
|
using (var sha256 = SHA256.Create())
|
|
{
|
|
var codeVerifierBytes = Encoding.UTF8.GetBytes(code);
|
|
var codeVerifierHash = sha256.ComputeHash(codeVerifierBytes);
|
|
return UrlSafeBase64Encode(codeVerifierHash);
|
|
}
|
|
}
|
|
|
|
string UrlSafeBase64Encode(byte[] input)
|
|
{
|
|
return Convert.ToBase64String(input)
|
|
.Replace('+', '-')
|
|
.Replace('/', '_')
|
|
.Replace("=", "");
|
|
}
|
|
|
|
public string ExtractAuthCode(IWebRequest<OAuthAuthCodeResponse> authCodeRequest)
|
|
{
|
|
try
|
|
{
|
|
var locationUri = new Uri(authCodeRequest.ResponseHeaders["Location"]);
|
|
return ExtractAuthCode(locationUri.ToString(), locationUri.Query);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_Logger.Error("Failed to extract auth code. " + ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
string ExtractAuthCode(string locationUri, string query)
|
|
{
|
|
var queryParams = HttpUtilities.ParseQueryString(query);
|
|
|
|
string code;
|
|
if (!queryParams.TryGetValue("code", out code))
|
|
{
|
|
m_Logger.Error($"Failed to extract auth code. Query parameter 'code' is not found. Location: {locationUri}");
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
public IWebRequest<OAuthTokenResponse> RequestOAuthToken(string authCode)
|
|
{
|
|
var payload = $"client_id={m_OAuthClientId}" +
|
|
"&grant_type=authorization_code" +
|
|
$"&code_verifier={m_SessionChallengeCode}" +
|
|
$"&code={authCode}";
|
|
|
|
return m_NetworkClient.PostForm<OAuthTokenResponse>(m_OAuthTokenUrl, payload, m_CommonHeaders);
|
|
}
|
|
|
|
public IWebRequest<OAuthTokenResponse> RefreshOAuthToken(string refreshToken)
|
|
{
|
|
var payload = "grant_type=refresh_token" +
|
|
$"&client_id={m_OAuthClientId}" +
|
|
$"&refresh_token={refreshToken}";
|
|
|
|
return m_NetworkClient.PostForm<OAuthTokenResponse>(m_OAuthTokenUrl, payload, m_CommonHeaders, 5);
|
|
}
|
|
|
|
public IWebRequest<OAuthTokenResponse> RevokeOAuthToken(string accessToken)
|
|
{
|
|
var payload = $"client_id={m_OAuthClientId}&token={accessToken}";
|
|
|
|
return m_NetworkClient.PostForm<OAuthTokenResponse>(m_OAuthRevokeTokenUrl, payload, m_CommonHeaders);
|
|
}
|
|
|
|
public string ExtractAccessToken(IWebRequest<OAuthTokenResponse> authCodeRequest)
|
|
{
|
|
return authCodeRequest.ResponseBody.AccessToken;
|
|
}
|
|
|
|
Dictionary<string, string> WithAccessToken(string accessToken)
|
|
{
|
|
return new Dictionary<string, string>(m_CommonHeaders) { ["Authorization"] = "Bearer " + accessToken };
|
|
}
|
|
}
|
|
}
|