簡體   English   中英

在 ASP.NET WebApi 2 中為移動應用實現外部身份驗證

[英]Implementing External Authentication for Mobile App in ASP.NET WebApi 2

我正在嘗試構建一個 API(使用 ASP.NET WebApi),它將被學校項目的本機移動應用程序使用。 (我不關心/開發移動應用程序,這個責任落在另一個成員身上)我現在需要實現基於令牌的 Facebook 登錄。 有很多教程可用於如何為基於瀏覽器的應用程序實現此功能(這非常簡單,而且大部分都是內置的),但我不認為我會遵循這將如何與本機應用程序一起使用。 我不明白的是重定向將如何工作?

根據此鏈接,我的服務器無需專門處理任何事情。 我想我不明白這將如何運作? 來自 Facebook 的代幣將如何處理?

另外,我應該實現令牌處理的哪一部分,我真的找不到關於 WebApi 外部登錄身份驗證的好文檔。

無論如何,如果有人可以向我指出發生的令牌交換的確切流程以及 ASP.NET 默認實現的內容,那將非常有幫助。

此外,對我來說最大的困惑是我不明白 Facebook 返回的令牌將如何處理。

  1. 我假設令牌將返回給客戶端(移動應用程序),我如何在我的服務器上訪問它?
  2. 如何從 facebook 的令牌創建本地令牌? 這一切都是由 ASP.NET 內部/自動完成的嗎?

如果這是我應該能夠弄清楚的事情,我很抱歉。 我確實做了很多研究,發現自己淹沒在(相關和不相關)信息中。 我想我什至不知道如何搜索我需要的信息。

我讀過的一些鏈接:

基於聲明和令牌的身份驗證 (ASP.NET Web API)

使用 ASP.NET Web API 2、Owin 和 Identity 的基於令牌的身份驗證

ASP.NET Web API 2 在 AngularJS 應用程序中使用 Facebook 和 Google 進行外部登錄

我不得不為我正在處理的應用程序做幾乎相同的事情。 我也很難找到有關它的信息。 似乎我發現的一切都接近我所需要的,但並不完全是解決方案。 我最終從一堆不同的博客文章、文章等中提取了點點滴滴,並將它們放在一起以使其工作。

我記得您發布的兩個鏈接“基於聲明和令牌的身份驗證”和“ASP.NET Web API 2 在 AngularJS 應用程序中使用 Facebook 和 Google 進行外部登錄”是包含有用信息的鏈接。

我不能給你一個全面的答案,因為我不記得我必須做的所有事情,我什至不明白我當時所做的一切,但我可​​以給你一個大概的想法。 你走在正確的軌道上。

本質上,我最終使用 Facebook 授予的令牌來確認他們已登錄到他們的 Facebook 帳戶,根據他們的 Facebook 用戶 ID 創建了一個用戶,並授予他們我自己的不記名令牌,他們可以使用該令牌訪問我的 API。

流程如下所示:

  1. 客戶端通過任何方法向 Facebook 進行身份驗證(我們使用oauth.io
    • Facebook 返回給他們一個代幣
  2. 客戶端將令牌信息發送到我的 WebApi 控制器的注冊端點
    • 該令牌使用 Facebook 的 Graph API 進行驗證,該 API 會返回用戶信息
    • 用戶是通過 ASP.NET Identity 在數據庫中創建的,其 Facebook 用戶 ID 作為密鑰
  3. 客戶端將令牌信息發送到我的 WebApi 控制器的身份驗證端點
    • 該令牌使用 Facebook 的 Graph API 進行驗證,該 API 會返回用戶信息
    • 用戶信息用於在數據庫中查找用戶,確認他們以前注冊過
    • ASP.NET Identity 用於為該用戶生成新令牌
    • 該令牌返回給客戶端
  4. ")客戶端在所有未來的 HTTP 請求中都包含一個授權標頭,其中包含我的服務授予的新令牌(例如“授權:不記名”)
    • 如果 WebApi 端點具有 [Authorize] 屬性,則 ASP.NET Identity 將自動驗證不記名令牌並在無效時拒絕訪問

最終有很多用於使用 ASP.NET Identity 實現 OAuth 內容的自定義代碼,您包含的那些鏈接向您展示了其中的一些。 希望這些信息能對您有所幫助,對不起,我幫不了什么忙。

我跟着這篇文章 流程基本是這樣

  • 服務器具有 facebook 密鑰,就像網絡登錄一樣
  • 該應用程序要求提供可用的社交登錄並顯示按鈕(我猜您可以對此進行硬編碼)
  • 當按下按鈕時,應用程序會打開瀏覽器並將 URL 設置為與指定社交登錄相關的 URL。 然后 ASP.NET 使用適當的挑戰將瀏覽器重定向到 facebook/google/whatever
  • 用戶可能已登錄或未登錄,也可能已授予您的應用程序權限。 在他授予權限后,facebook 重定向回提供的回調 URL
  • 此時,您可以從 SignInManager 獲取外部登錄信息並檢查用戶是否已經存在以及是否應該創建一個新帳戶
  • 最后生成一個令牌,瀏覽器被重定向到放置令牌的 URL。 應用程序從 URL 獲取令牌並關閉瀏覽器。 使用令牌繼續處理 API 請求。

老實說,我不知道這種方法是否合法......

操作按鈕的代碼應該重定向到:

public async Task<IEnumerable<ExternalLoginDto>> GetExternalLogins(string returnUrl, bool generateState = false)
{
    IEnumerable<AuthenticationScheme> loginProviders = await SignInManager.GetExternalAuthenticationSchemesAsync();
    var logins = new List<ExternalLoginDto>();

    string state;

    if (generateState)
    {
        const int strengthInBits = 256;
        state = RandomOAuthStateGenerator.Generate(strengthInBits);
    }
    else
    {
        state = null;
    }

    foreach (AuthenticationScheme authenticationScheme in loginProviders)
    {
        var routeValues = new
        {
            provider = authenticationScheme.Name,
            response_type = "token",
            client_id = Configuration["Jwt:Issuer"],
            redirect_uri = $"{Request.Scheme}//{Request.Host}{returnUrl}",
            state = state
        };

        var login = new ExternalLoginDto
        {
            Name = authenticationScheme.DisplayName,
            Url = Url.RouteUrl("ExternalLogin", routeValues),
            State = state
        };

        logins.Add(login);
    }

    return logins;
}

回調動作的代碼:

[Authorize(AuthenticationSchemes = "Identity.External")]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IActionResult> GetExternalLogin(string provider, string state = null, string client_id = null, string error = null)
{
    if (error != null)
    {
        ThrowBadRequest(error);
    }

    if (!User.Identity.IsAuthenticated)
    {
        return new ChallengeResult(provider);
    }

    string providerKey = User.FindFirstValue(ClaimTypes.NameIdentifier);

    var externalLoginInfo = new ExternalLoginInfo(User, User.Identity.AuthenticationType, providerKey, User.Identity.AuthenticationType);

    if (externalLoginInfo.LoginProvider != provider)
    {
        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
        return new ChallengeResult(provider);
    }

    var userLoginInfo = new UserLoginInfo(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, externalLoginInfo.ProviderDisplayName);
    User user = await UserManager.FindByLoginAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey);

    if (client_id != Configuration["Jwt:Issuer"])
    {
        return Redirect($"/#error=invalid_client_id_{client_id}");
    }

    if (user != null)
    {
        return await LoginWithLocalUser(user, state);
    }
    else
    {
        string email = null;
        string firstName = null;
        string lastName = null;

        IEnumerable<Claim> claims = externalLoginInfo.Principal.Claims;
        if (externalLoginInfo.LoginProvider == "Google")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
            firstName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
            lastName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
        }
        else if (externalLoginInfo.LoginProvider == "Facebook")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;

            string[] nameParts = claims.First(c => c.Type == ClaimTypes.Name)?.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            firstName = nameParts?.First();
            lastName = nameParts?.Last();
        }

        //some fallback just in case
        firstName ??= externalLoginInfo.Principal.Identity.Name;
        lastName ??= externalLoginInfo.Principal.Identity.Name;

        user = new User
        {
            UserName = email,
            Email = email,
            FirstName = firstName,
            LastName = lastName,
            EmailConfirmed = true //if the user logs in with Facebook consider the e-mail confirmed
        };

        IdentityResult userCreationResult = await UserManager.CreateAsync(user);
        if (userCreationResult.Succeeded)
        {
            userCreationResult = await UserManager.AddLoginAsync(user, userLoginInfo);
            if (userCreationResult.Succeeded)
            {
                return await LoginWithLocalUser(user, state);
            }
        }

        string identityErrrors = String.Join(" ", userCreationResult.Errors.Select(ie => ie.Description));
        Logger.LogWarning($"Error registering user with external login. Email:{email}, Errors:" + Environment.NewLine + identityErrrors);
        return Redirect($"/#error={identityErrrors}");
    }
}

private async Task<RedirectResult> LoginWithLocalUser(User user, string state)
{
    await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

    DateTime expirationDate = DateTime.UtcNow.AddDays(365);

    string token = user.GenerateJwtToken(Configuration["Jwt:Key"], Configuration["Jwt:Issuer"], expirationDate);
    return Redirect($"/#access_token={token}&token_type=bearer&expires_in={(int)(expirationDate - DateTime.UtcNow).TotalSeconds}&state={state}");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM