通过社交网络进行 OWIN OAuth2 身份验证






4.85/5 (17投票s)
Web API通过Facebook、Google、Microsoft支持的OWIN OAuth2中间件进行基于令牌的身份验证。从社交网络提取额外的用户信息。
引言
在本文中,您可以找到通过社交网络(Facebook,Google和Microsoft)验证用户的代码示例、演示和解释。如何提取有关用户的其他信息,例如头像、电子邮件和全名。
您可以在我的网站上查看演示并下载最新版本的代码:SupperSlonic.com
基本概念
Owin 中间件
Owin中间件模块负责处理与外部身份验证提供程序(如Facebook、Google等)的身份验证,并通过cookie建立应用程序会话。在所有后续调用中,应用程序cookie中间件提取传入应用程序cookie的内容并设置当前上下文的声明标识。
什么是令牌?
令牌是一个字符串值,代表一个加密的System.Security.Claims
列表。您可以使用任何您喜欢的声明。我的应用程序有自己的在整个项目中使用的声明列表。我们称之为应用程序声明。
应用程序声明是由OWIN加密到颁发令牌中的定义良好的声明列表。每次服务收到令牌时,它都会尝试使用其IIS机器密钥解密它,将其还原为声明列表并填充User.Identity对象。
要确定它是外部持有者令牌还是本地令牌,它会检查声明的Issuer字段。对于本地令牌,它必须始终是ClaimsIdentity.DefaultIssuer
。
请注意,OWIN使用IIS机器密钥进行加密,因此您必须应用一些自定义解决方案才能在多个WEB服务之间使用相同的令牌。
我是如何构建我的应用程序声明列表的
private static ClaimsIdentity CreateIdentity(ClaimsMapper claimsMapper, string authenticationType)
{
IList claims = new List();
claims.Add(new Claim(ClaimTypes.NameIdentifier, claimsMapper.Id, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypes.Email, claimsMapper.Email, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypes.GivenName, claimsMapper.FullName, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypes.Sid, claimsMapper.Sid, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypes.Version, claimsMapper.Version, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypeIsVerified, claimsMapper.IsVerified, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
claims.Add(new Claim(ClaimTypeAvatarUrl, claimsMapper.AvatarUrl, null, claimsMapper.Issuer, claimsMapper.OriginalIssuer));
return new ClaimsIdentity(claims, authenticationType);
}
因此,我总是可以从我的WEB服务颁发的任何令牌中获取所有这些信息。ClaimsMapper
是一种抽象策略,它知道如何将不同的数据模型映射到我的声明列表。
身份验证流程
OAuth2 身份验证
应用程序如何从Facebook、Google等获取用户信息?
从外部提供程序(Facebook、Google等)收到的所有用户信息都加密在外部cookie中。
这是应用程序与外部提供程序之间通信流程的描述
// GET api/Account/ExternalLogin
[AllowAnonymous]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)] //authenticated by external provider
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] //refresh token support
[Route("externalLogin", Name = "externalLogin")]
public async Task GetExternalLogin(string provider, string error = null)
{
if (error != null)
{
return Redirect(Url.Content("~/") + "#error=" + Uri.EscapeDataString(error));
}
ExternalLoginProvider loginProvider;
if (!Enum.TryParse(provider, ignoreCase: true, result: out loginProvider) ||
loginProvider == ExternalLoginProvider.None)
{
//Unsupported login provider
return InternalServerError();
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(loginProvider, this);
}
ExternalLoginModel externalLogin = ExternalLoginModel.FromIdentity(User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.Provider != loginProvider)
{
Request.GetOwinContext().Authentication.SignOut(
DefaultAuthenticationTypes.ExternalCookie,
OAuthDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
return new ChallengeResult(loginProvider, this);
}
User user = await this.UserProvider.FindAsync(externalLogin.Provider, externalLogin.ProviderKey);
if (user != null)
{
OwinHelper.SingIn(Request.GetOwinContext(), user, externalLogin);
}
else
{
OwinHelper.SingIn(Request.GetOwinContext(), externalLogin);
}
return Ok();
}
Facebook用户数据提取
public class FacebookOAuthProvider : FacebookAuthenticationProvider
{
private const string ApiBaseUrl = "https://graph.facebook.com";
public override Task Authenticated(FacebookAuthenticatedContext context)
{
string avatarUrl = GetAvatarUrl(context.User.GetValue("id").ToString(), 240);
context.Identity.AddClaim(
new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));
return base.Authenticated(context);
}
public static string GetAvatarUrl(string facebookUserId, int size)
{
return string.Format("{0}/{1}/picture?width={2}&height={2}",
ApiBaseUrl,
facebookUserId,
size);
}
}
Google用户数据提取
public class GoogleOAuthProvider : GoogleOAuth2AuthenticationProvider
{
public override Task Authenticated(GoogleOAuth2AuthenticatedContext context)
{
string avatarUrl = context.User
.SelectToken("image.url")
.ToString()
.Replace("sz=50", "sz=240");
context.Identity.AddClaim(
new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));
return base.Authenticated(context);
}
}
Microsoft用户数据提取
public class MicrosoftOAuthProvider : MicrosoftAccountAuthenticationProvider
{
public override void ApplyRedirect(MicrosoftAccountApplyRedirectContext context)
{
context = new MicrosoftAccountApplyRedirectContext(
context.OwinContext,
context.Options,
context.Properties,
context.RedirectUri + "&display=touch"); //Mobile devices support
base.ApplyRedirect(context);
}
public override Task Authenticated(MicrosoftAccountAuthenticatedContext context)
{
string avatarUrl = string.Format("https://apis.live.net/v5.0/{0}/picture",
context.User.GetValue("id").ToString());
context.Identity.AddClaim(
new Claim(OwinHelper.ClaimTypeAvatarUrl, avatarUrl));
return base.Authenticated(context);
}
}
令牌发放
一旦用户通过身份验证,应用程序有三种不同的流程来发放令牌(第10步有详细视图)
- 新用户通过外部提供程序授权:
在颁发外部持有者令牌后,用户可以使用此令牌在应用程序中注册,然后再次重新授权,以获取新令牌(本地持有者)。
public class NotRegisteredExternal : ClaimsMapper { public NotRegisteredExternal(ExternalLoginModel extLogin) { this.Id = string.Empty; this.Email = extLogin.Email ?? string.Empty; this.FullName = extLogin.FullName ?? string.Empty; this.AvatarUrl = extLogin.AvatarUrl ?? string.Empty; this.Sid = extLogin.ProviderKey; this.Version = string.Empty; this.IsVerified = false.ToString(); this.Issuer = extLogin.Provider.ToString(); this.OriginalIssuer = this.Issuer; } }
- 现有用户通过外部提供程序授权:
public class RegisteredExternal : ClaimsMapper { public RegisteredExternal(User user, ExternalLoginModel extLogin) { this.Id = user.Id.ToString(); this.Email = user.Email; this.FullName = user.FullName ?? string.Empty; this.AvatarUrl = UserProvider.GetAvatarUrl(user); this.Sid = extLogin.ProviderKey; this.Version = this.GetVersion(user.TimeStamp); this.IsVerified = user.IsVerified.ToString(); this.Issuer = ClaimsIdentity.DefaultIssuer; this.OriginalIssuer = extLogin.Provider.ToString(); } }
- 现有用户通过登录/密码授权:
public class RegisteredLocal : ClaimsMapper { public RegisteredLocal(User user) { this.Id = user.Id.ToString(); this.Email = user.Email; this.FullName = user.FullName ?? string.Empty; this.AvatarUrl = UserProvider.GetAvatarUrl(user); this.Sid = string.Empty; this.Version = this.GetVersion(user.TimeStamp); this.IsVerified = user.IsVerified.ToString(); this.Issuer = ClaimsIdentity.DefaultIssuer; this.OriginalIssuer = ClaimsIdentity.DefaultIssuer; } }
在外部提供程序注册您的应用程序
Facebook 配置
- 导航到Facebook开发者页面并输入您的Facebook凭据登录;
- 如果您尚未注册为Facebook开发者,请点击“注册为开发者”并按照说明进行注册;
- 在我的应用选项卡下,点击+ 添加新应用按钮:
- 选择网站作为应用程序平台:
- 输入应用名称和类别,然后点击创建应用。
这在Facebook上必须是唯一的。应用命名空间是您的应用将用于访问Facebook应用程序进行身份验证的URL的一部分(例如,https://apps.facebook.com/{应用命名空间})。如果您不指定应用命名空间,则将使用应用ID作为URL。应用ID是一个长系统生成的数字,您将在下一步中看到。 - 在页面的基本设置部分
- 输入联系电子邮件;
- 输入将向Facebook发送请求的站点URL。
请注意,只有您才能使用您注册的电子邮件别名进行身份验证。其他用户和测试帐户将无法注册。
您可以在角色菜单下授予测试用户访问应用程序的权限。
对于所有其他Facebook帐户,您的应用程序必须获得Facebook的批准。有关进一步说明,请查看状态与审核菜单。 - 要禁用应用的沙盒模式,请转到左侧的状态与审核菜单并选择是:
Google 配置
- 导航到Google开发者控制台;
- 点击创建项目按钮并输入项目名称和ID(您可以使用默认值)。几秒钟后,新项目将创建成功,您的浏览器将显示新项目页面;
- 在左侧选项卡中,点击API和身份验证,然后点击同意屏幕
- 输入电子邮件地址;
- 输入产品名称
- 在左侧选项卡中,点击API和身份验证,然后点击API
- 启用Google+ API以支持用户头像访问
- 在左侧选项卡中,点击API和身份验证,然后点击凭据。
- 在OAuth下点击创建新的客户端ID
- 在创建客户端ID对话框中,将应用程序类型保持默认Web应用程序;
- 将授权JavaScript来源设置为服务的SSL URL,例如:https://supperslonic.com/;
- 将授权重定向URI设置为:https://supperslonic.com/signin-google。
- 将AppId和App Secret复制并粘贴到Google的Credentials.resx文件中。
Microsoft 配置
- 导航到Microsoft开发者帐户;
- 点击创建应用程序引用;
- 在基本信息中输入有效的应用程序名称服务URL:
- 在API设置中选择它是移动应用程序并输入有效的重定向URL
- 请注意在您的重定向URL中添加signin-microsoft。
- 在应用设置中,将AppId和App Secret复制并粘贴到Microsoft的Credentials.resx文件中。