0

使用缓存Cognito身份当我第一次登录到我的应用程序,我通过下面的代码:从Xamarin

 auth = new Xamarin.Auth.OAuth2Authenticator(
     "my-google-client-id.apps.googleusercontent.com", 
     string.Empty, 
     "openid", 
     new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"), 
     new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"), 
     new System.Uri("https://www.googleapis.com/oauth2/v4/token"), 
     isUsingNativeUI: true); 

    auth.Completed += Auth_Completed; 
    StartActivity(auth.GetUI(this)); 

触发此活动:

[Activity(Label = "GoodleAuthInterceptor")] 
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, 
    DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })] 
public class GoodleAuthInterceptor : Activity 
{ 
    protected override void OnCreate(Bundle savedInstanceState) 
    { 
    base.OnCreate(savedInstanceState); 
    Android.Net.Uri uri_android = Intent.Data; 
    Uri uri_netfx = new Uri(uri_android.ToString()); 
    MainActivity.auth?.OnPageLoading(uri_netfx); 
    Finish(); 
    } 
} 

最后这个代码链接注册到Cognito:

private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e) 
    { 
    if (e.IsAuthenticated) 
    { 
     var idToken = e.Account.Properties["id_token"]; 

     credentials.AddLogin("accounts.google.com", idToken); 
     AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2); 
     var req = new Amazon.CognitoIdentity.Model.GetIdRequest(); 
     req.Logins.Add("accounts.google.com", idToken); 
     req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx"; 
     cli.GetIdAsync(req).ContinueWith((task) => 
     { 
      if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null)) 
      { 
       ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId)); 
      } 
      else 
       ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message); 
     }); 
    } 
    else 
     ShowMessage("Login cancelled"); 
    } 

这一切都很好,登录后,我可以使用我的身份/证书从DynamoDB检索数据。有了这个对象:

Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2); 

第二次,我跑我的应用程序,运行此代码:

if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0) 
{ 
    if (!bDidLogin) 
    { 
     var idToken = credentials.GetIdentityId(); 
     ShowMessage(string.Format("I still remember you're {0} ", idToken)); 

如果我尝试使用DynamoDB使用的凭据(或任何东西,我假设),在这一点上,我收到我无法访问身份的错误。我必须注销(credentials.Clear())并再次登录以获取正确的凭据。 我可以要求用户在每次运行应用程序时都要经历整个登录过程,但这真的很痛苦,因为Google登录过程要求用户知道如何在认证后手动关闭Web浏览器以返回应用程序。有什么我错过了缓存凭据的目的和用法?当我使用大多数应用程序时,他们不要求我每次都登录到我的Google帐户,并关闭网络浏览器以访问其服务器资源。

+2

1)在oauth2登录完成后,您可以使用基于LaunchMode.SingleTask的Activity来关闭Chrome(或Firefox,Samsung等)自定义选项卡,方法是在同一活动中启动OAuth2Authenticator,而不是“MainActivity”(Xamarin.Auth文档(readme.md)对于它们如何显示你的设置是错误的)。 2)你是说第一次登录后,缓存的凭证是空的还是令牌丢失? – SushiHangover

+0

@SushiHangover我相信这些凭据是存在的,因为它告诉我我的ID,并告诉我如果我尝试登录到另一个Google帐户(添加具有不同ID的另一个登录名),则不能使用其他身份。但是,如果我尝试使用凭据,则表示我无法访问身份。我对此非常陌生,所以我不确定这些部分如何组合在一起,或者这些错误是什么意思。 – BlueMonkMN

+0

@SushiHangover我能够获得'LaunchMode.SingleTask'来改善行为,但我仍然不清楚缓存凭据的性质或目的,以及每次运行应用程序时是否需要提示输入Google帐户。它记住我的身份标记,但显然不需要通过身份验证。 – BlueMonkMN

回答

0

它看起来像刷新标记需要提交回OAuth2提供者以获取更新的id标记添加到凭证对象。首先,我添加了一些代码来保存,并在config.json文件加载refresh_token:

private Dictionary<string, string> config; 
const string CONFIG_FILE = "config.json"; 

private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e) 
{ 
    if (e.IsAuthenticated) 
    { 
     var idToken = e.Account.Properties["id_token"]; 
     if (e.Account.Properties.ContainsKey("refresh_token")) 
     { 
     if (config == null) 
      config = new Dictionary<string, string>(); 
     config["refresh_token"] = e.Account.Properties["refresh_token"]; 
     WriteConfig(); 
     } 

     credentials.AddLogin("accounts.google.com", idToken); 
     CognitoLogin(idToken).ContinueWith((t) => 
     { 
     try 
     { 
      t.Wait(); 
     } 
     catch (Exception ex) 
     { 
      ShowMessage(ex.Message); 
     } 
     }); 
    } 
    else 
     ShowMessage("Login cancelled"); 
} 

void WriteConfig() 
{ 
    using (var configWriter = new System.IO.StreamWriter(
     Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private))) 
    { 
     configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config)); 
     configWriter.Close(); 
    } 
} 

public void Login() 
{ 
    try 
    { 
     if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0) 
     { 
     if (!bDidLogin) 
     { 
      var idToken = credentials.GetIdentityId(); 
      if (ReadConfig()) 
      { 
       LoginRefreshAsync().ContinueWith((t) => 
       { 
        try 
        { 
        t.Wait(); 
        if (!t.Result) 
         FullLogin(); 
        } 
        catch (Exception ex) 
        { 
        ShowMessage(ex.Message); 
        } 
       }); 
      } 
      else 
      { 
       credentials.Clear(); 
       FullLogin(); 
      } 
     } 
     } 
     else 
     FullLogin(); 
     bDidLogin = true; 
    } 
    catch(Exception ex) 
    { 
     ShowMessage(string.Format("Error logging in: {0}", ex.Message)); 
    } 
} 

private bool ReadConfig() 
{ 
    bool bFound = false; 
    foreach (string filename in Application.FileList()) 
     if (string.Compare(filename, CONFIG_FILE, true) == 0) 
     { 
     bFound = true; 
     break; 
     } 
    if (!bFound) 
     return false; 
    using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE))) 
    { 
     config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd()); 
     return true; 
    } 
} 

然后重构发起的交互式登录到一个单独的函数代码:

public void FullLogin() 
{ 
    auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid", 
     new Uri("https://accounts.google.com/o/oauth2/v2/auth"), 
     new Uri("com.enigmadream.storyvoque:/oauth2redirect"), 
     new Uri("https://accounts.google.com/o/oauth2/token"), 
     isUsingNativeUI: true); 

    auth.Completed += Auth_Completed; 
    StartActivity(auth.GetUI(this)); 
} 

重构的代码,检索Cognito的身份进入其自身的功能:

private async Task CognitoLogin(string idToken) 
{ 
    AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2); 
    var req = new Amazon.CognitoIdentity.Model.GetIdRequest(); 
    req.Logins.Add("accounts.google.com", idToken); 
    req.IdentityPoolId = ID_POOL; 
    try 
    { 
     var result = await cli.GetIdAsync(req); 
     ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId)); 
    } 
    catch (Exception ex) 
    { 
     ShowMessage(ex.Message); 
    } 
} 

最后实现的,可以获取基于刷新令牌的新令牌的功能,将其插入转换成当前的Cognito凭据,并获取更新的Cognito身份

private async Task<bool> LoginRefreshAsync() 
{ 
    string tokenUrl = "https://accounts.google.com/o/oauth2/token"; 
    try 
    { 
     using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient()) 
     { 
     string contentString = string.Format(
      "client_id={0}&grant_type=refresh_token&refresh_token={1}&", 
      Uri.EscapeDataString(CLIENTID_GOOGLE), 
      Uri.EscapeDataString(config["refresh_token"])); 
     System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
      System.Text.Encoding.UTF8.GetBytes(contentString)); 
     content.Headers.Add("content-type", "application/x-www-form-urlencoded"); 
     System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content); 
     string result = await msg.Content.ReadAsStringAsync(); 
     string idToken = System.Json.JsonValue.Parse(result)["id_token"]; 
     credentials.AddLogin("accounts.google.com", idToken); 
     /* EDIT -- discovered this is not necessary! */ 
     // await CognitoLogin(idToken); 
     return true; 
     } 
    } 
    catch (Exception ex) 
    { 
     ShowMessage(ex.Message); 
     return false; 
    } 
} 

我不确定这是最佳的甚至正确的,但它似乎工作。我可以使用生成的凭据访问DynamoDB,而不必再次提示用户输入权限/凭据。

0

有一个非常不同的解决方案,我试图适应其他答案。但它是如此不同,我把它作为一个单独的答案添加。

看起来问题与需要显式使用刷新令牌来获取更新的访问令牌(我认为这是隐式完成的)有关,而是需要记住身份令牌。因此,除了手动应用刷新令牌的所有复杂性之外,所需要的只是存储身份令牌(可以通过类似于如何存储刷新令牌的方式来完成)。然后,我们只需要将相同的身份标识添加回凭证对象。

if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0) 
{ 
    if (config.Read()) 
    { 
     if (config["id_token"] != null) 
     credentials.AddLogin(currentProvider.Name, config["id_token"]); 

编辑:需要使用令牌刷新确实仍然存在的问题。如果令牌未过期,则此代码有效,但在令牌过期后尝试使用这些凭据将失败,因此在某些情况下仍需要某种方式使用刷新令牌。