如何在ASP.NET Boilerplate中设置社交登录?

7
我正在尝试通过Google验证用户。 我正在使用Vue和ASP.NET Core的ABP启动模板。
目前,我已经完成了以下工作:
我在Web.Core中创建了一个名为GoogleAuthProviderApi的API。
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Google;
using Newtonsoft.Json.Linq;

namespace Mindbus.MindbooksSEO.Authentication.External.Google
{
    public class GoogleAuthProviderApi : ExternalAuthProviderApiBase
    {
        public const string Name = "Google";

        public override async Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.UserAgent.ParseAdd("Microsoft ASP.NET Core OAuth middleware");
                client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
                client.Timeout = TimeSpan.FromSeconds(30);
                client.MaxResponseContentBufferSize = 1024 * 1024 * 10; // 10 MB

                var request = new HttpRequestMessage(HttpMethod.Get, GoogleDefaults.UserInformationEndpoint);
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessCode);

                var response = await client.SendAsync(request);

                response.EnsureSuccessStatusCode();

                var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

                return new ExternalAuthUserInfo
                {
                    //Name = GoogleHelper.GetName(payload),
                    EmailAddress = GoogleHelper.GetEmail(payload),
                    //Surname = GoogleHelper.GetFamilyName(payload),
                    //ProviderKey = GoogleHelper.GetId(payload),
                    Provider = Name
                };
            }
        }
    }
}

我在Web.Host的AuthConfigurer.cs文件中注册了Google外部认证。
if (bool.Parse(configuration["Authentication:Google:IsEnabled"]))
{
    services.AddAuthentication().AddGoogle(googleOptions =>
    {
        googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
        googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
    });
}

我已经将设置添加到Web.Host中的appsettings.json文件中,并在Secret Manager工具中创建了相应的秘密(ClientId和ClientSecret)。
我使用RequireHttpsAttribute强制API使用SSL。
我在[ProjectName]WebCoreModule.cs中注册了GoogleAuthProviderApi。
public override void PreInitialize()
{
    Configuration.DefaultNameOrConnectionString = _appConfiguration.GetConnectionString(
        MindbooksSEOConsts.ConnectionStringName
    );

    // Use database for language management
    Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization();

    Configuration.Modules.AbpAspNetCore()
            .CreateControllersForAppServices(
                typeof(MindbooksSEOApplicationModule).GetAssembly()
            );

    ConfigureTokenAuth();

    Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<GoogleAuthProviderApi>();
}

我不知道我在这里缺少什么,也不知道应该期望什么。
我本以为调用 api/TokenAuth/GetExternalAuthenticationProviders 端点至少会给我一个包含 Google 的列表,但是这个请求返回的结果是一个空数组。
此外,对于像 Google 和 Facebook 这样的 OAuth 提供者,这种外部身份验证的范围对我来说有点不清楚。看起来你要么有用于服务器端使用的 OAuth,在这种情况下,我不明白为什么你要通过 API 暴露它的一部分。要么你有用于 JavaScript Web 应用程序的 OAuth,在这种情况下,你不需要自己服务器上的 API 端点,你只需要通过 Web 应用程序客户端在整个过程中处理即可。
那么,外部身份验证 API 端点确切的目的是什么?是让你自己的服务器充当身份验证代理吗?所以你既可以在客户端和服务器端使用外部 (Google) API?
更新1:
评论要求我澄清一些内容。
#1: 如果我在 Postman 中添加 Abp.TenantId 标头,响应仍然相同:
GET /api/TokenAuth/GetExternalAuthenticationProviders HTTP/1.1
Host: localhost:44300
Accept: application/json
Abp.TenantId: 2
Cache-Control: no-cache
Postman-Token: 0cb72e57-4b9a-474d-b60d-492fa727a7a2

#2: Swagger中控制台的“技巧”导致错误:

abp.swagger.login()
undefined
VM40:49 POST https://localhost:44300/api/TokenAuth/Authenticate 500 ()
abp.swagger.login @ VM40:49
(anonymous) @ VM84:1
abp.swagger.addAuthToken()
false

更新2

我认为GoogleAuthProviderApi出了些问题。在我让调试器中断所有CLR异常后,我捕获了以下错误:

'Mindbus.MindbooksSEO.Authentication.External.Google.GoogleAuthProviderApi' to type
 'Abp.Authorization.Users.IExternalAuthenticationSource`2
[Mindbus.MindbooksSEO.MultiTenancy.Tenant,
Mindbus.MindbooksSEO.Authorization.Users.User]'.'

1
在调用 GetExternalAuthenticationProviders 方法时,您是否发送了 Abp.TenantId 标头? - aaron
不,我没有这样做,因为在Swagger UI中我看不到如何实现。让我试试Postman,看看它能给我什么结果,然后再回复你。 - gijswijs
对于您的最后一个问题,是的,您可以使用自己的服务器作为认证机构,就像Facebook登录一样。而且,您可以为其他网站创建Gijswijs登录。 - Alper Ebicoglu
只有在租户存在时(主机端不支持),GetExternalAuthenticationProviders 才会返回值,因此请发送 Abp.TenantId。 - Alper Ebicoglu
GetExternalAuthenticationProviders只有在租户存在时才返回值(在主机端不支持),因此请发送Abp.TenantId。为什么没有登录就不能工作呢? - Worthy7
2个回答

4

ASP.NET Core 1.x or MVC 5

  1. Note that the configuration for Social Login providers (e.g. Google) is quite different from External Authentication sources (e.g. LDAP). So, remove this line:

    Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<GoogleAuthProviderApi>();

  2. Observe that GetExternalAuthenticationProviders looks in IExternalAuthConfiguration.
    So, configure IExternalAuthConfiguration in PostInitialize method of *WebHostModule:

    if (bool.Parse(configuration["Authentication:Google:IsEnabled"]))
    {
        var externalAuthConfiguration = IocManager.Resolve<IExternalAuthConfiguration>();
    
        externalAuthConfiguration.Providers.Add(
            new ExternalLoginProviderInfo(
                GoogleAuthProviderApi.Name,
                configuration["Authentication:Google:ClientId"],
                configuration["Authentication:Google:ClientSecret"],
                typeof(GoogleAuthProviderApi)
            )
        );
    }
    

ASP.NET Core 2.x

虽然上述处理社交登录提供者的方式可能有效,但已不再推荐使用

内置的.AddGoogle方法:

if (bool.Parse(configuration["Authentication:Google:IsEnabled"]))
{
    services.AddAuthentication().AddGoogle(googleOptions =>
    {
        googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
        googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
    });
}

...被用来与以下内容配合使用:

var result = await _signInManager.ExternalLoginSignInAsync(
    info.LoginProvider,
    info.ProviderKey,
    isPersistent: false,
    bypassTwoFactor : true
);

获取外部认证方案的方法如下:

var schemes = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

您可以修改 GetExternalAuthenticationProviders 方法以返回此数据。

感谢您的详细回复。我想我理解了。另外一个问题是:外部认证方案无法映射到ExternalLoginProviderInfoModel。您是否建议修改ExternalLoginProviderInfoModel? - gijswijs
@aaron 但是我认为 ExternalLogin 和社交登录没有任何关系? - Worthy7
1
我遇到了这个问题,虽然它有所帮助,但仍然需要做很多的解决工作。文档真的非常、非常有帮助。 - MattSull
你可以对那个问题进行评论。 - aaron
这是低优先级的,因为它没有出现故障,而且信息已经以某种形式存在。收益并不足以证明付出的努力。社区贡献总是受欢迎的。 - aaron
显示剩余5条评论

2

对于那些访问这个主题却找不到答案的人(像我一样)。

aspnetcore 2.2和abp 4.5.0的Facebook工作示例

*WebHostModule.cs

最初的回答

public override void PostInitialize()
        {
            var externalAuthConfiguration = IocManager.Resolve<IExternalAuthConfiguration>();
            externalAuthConfiguration.Providers.Add(
                 new ExternalLoginProviderInfo(
                    FacebookAuthProvider.Name,
                    configuration["Authentication:Facebook:ClientId"],
                    configuration["Authentication:Facebook:Secret"],
                    typeof(FacebookAuthProvider)
                )
            );           
        }

*FacebookAuthProvider.cs

public class FacebookAuthProvider: ExternalAuthProviderApiBase
    {
        private static readonly HttpClient Client = new HttpClient();
        public const string Name = "Facebook";
        public override async Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
        {
            //gen app access token
            var appAccessTokenResponse = await Client.GetStringAsync("https://graph.facebook.com/oauth/access_token" +
              "?client_id=" + ProviderInfo.ClientId +
              "&client_secret=" + ProviderInfo.ClientSecret +
              "&grant_type=client_credentials");
            var appAccessToken = JsonConvert.DeserializeObject<FacebookAppAccessToken>(appAccessTokenResponse);
            //validate user access token
            var userAccessTokenValidationResponse = await Client.GetStringAsync("https://graph.facebook.com/v3.2/debug_token" +
                "?input_token="+ accessCode +
                "&access_token="+ appAccessToken.AccessToken);
            var userAccessTokenValidation = JsonConvert.DeserializeObject<FacebookUserAccessTokenValidation>(userAccessTokenValidationResponse);
            if (!userAccessTokenValidation.Data.IsValid)
            {
                throw new ArgumentException("login_failure Invalid facebook token.");
            }

            //get userinfo
            var userInfoResponse = await Client.GetStringAsync($"https://graph.facebook.com/v3.2/me?fields=id,email,first_name,last_name&access_token={accessCode}");
            var userInfo = JsonConvert.DeserializeObject<FacebookUserData>(userInfoResponse);

            return new ExternalAuthUserInfo
            {
                Name = userInfo.FirstName,
                EmailAddress = userInfo.Email,
                Surname=userInfo.LastName,
                Provider=Name,
                ProviderKey=userInfo.Id.ToString()
            };

        }
    }

最初的回答
模型
internal class FacebookUserData
    {
        public long Id { get; set; }
        public string Email { get; set; }
        public string Name { get; set; }
        [JsonProperty("first_name")]
        public string FirstName { get; set; }
        [JsonProperty("last_name")]
        public string LastName { get; set; }
        public string Gender { get; set; }
        public string Locale { get; set; }
        public FacebookPictureData Picture { get; set; }
    }

    internal class FacebookPictureData
    {
        public FacebookPicture Data { get; set; }
    }

    internal class FacebookPicture
    {
        public int Height { get; set; }
        public int Width { get; set; }
        [JsonProperty("is_silhouette")]
        public bool IsSilhouette { get; set; }
        public string Url { get; set; }
    }

    internal class FacebookUserAccessTokenData
    {
        [JsonProperty("app_id")]
        public long AppId { get; set; }
        public string Type { get; set; }
        public string Application { get; set; }
        [JsonProperty("expires_at")]
        public long ExpiresAt { get; set; }
        [JsonProperty("is_valid")]
        public bool IsValid { get; set; }
        [JsonProperty("user_id")]
        public long UserId { get; set; }
    }

    internal class FacebookUserAccessTokenValidation
    {
        public FacebookUserAccessTokenData Data { get; set; }
    }

    internal class FacebookAppAccessToken
    {
        [JsonProperty("token_type")]
        public string TokenType { get; set; }
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }
    }

谢谢你。你能解释一下你是如何管理Facebook重定向的吗?你是把重定向到/api/TokenAuth/ExternalAuthenticate吗? - Pier-Lionel Sgard
我使用了 Facebook 的 "按钮插件" 在前端获取访问代码,并使用上面的示例来证明代码并获取用户信息。我只是从 JavaScript 中发送此数据,而没有重定向。 - crunchysea65
正如您所看到的,ExternalAuthenticate方法接受提供程序名称、用户ID和访问代码。因此,您不能简单地将Facebook重定向到此方法。 - crunchysea65

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接