设置:仅具有Web API的新MVC5项目。添加了Facebook AppId和Secret。
我可以Token
通过传递用户名和密码来从端点获取Web API的令牌。然后使用该令牌进行进一步调用。
但是我想在iOS应用中的Facebook SDK的帮助下注册新用户。我正在使用Facebook SDK来获取访问令牌。(假设此时,我有一个访问令牌)。
我知道的下一件事是api/Account/RegisterExternal
通过在Authorization
标头中传递此令牌来调用端点,Bearer [Access Token]
但这会导致500服务器错误。
我想我知道原因,Cookie丢失了。我使用来自Fidler的cookie进行了相同的调用,并且效果很好。(通过转到ExternalLogins
端点提供的URL来接收Cookie 。)由于缺少Cookie await Authentication.GetExternalLoginInfoAsync();
,RegisterExternal操作内将返回null。
// POST api/Account/RegisterExternal
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
我不想对Web API进行3次调用以请求外部登录,然后转到该URL并在Web浏览器中对Facebook访问令牌进行身份验证,然后使用该访问令牌和需要收集的Cookie调用RegisterExternal端点在这些通话之间。
正如我说的,除了Facebook ID之外,我没有更改模板中的任何内容。代码仍然如下。
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
app.UseFacebookAuthentication(
appId: "xxxxxxxxxxxxxxx",
appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx");
}
}
据我所知,Web API不需要Cookie,当我从Token
终结点计算机获取本地令牌时,它看起来是正确的,但是为什么在进行ExternalRegister
WebApiConfig类时它首先需要Cookie,所以看起来像这样,并且应该config.SuppressDefaultHostAuthentication();
避免任何Cookie的需求
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
我不知道是否遗漏了这里。.我的意图是不需要在本机iOS应用中使用Web浏览器来获取令牌。那就是Facebook SDK来获取访问令牌,并使用该调用RegisterExternal
来获取本地令牌并创建该用户身份。
我做了家庭作业,但仍然坚持这种想法。感激不尽!
我误以为它接受带有cookie的Social Token!它不直接接受任何外部令牌。
问题是……MVC 5正在为我们处理所有事情,即从社交媒体收集令牌并对其进行验证/处理。之后,它将生成一个本地令牌。
该RegisterExternal
方法还需要维护cookie,而解决方案则不需要。
我写了一篇博客文章,将详细解释。在下面添加了直接答案。我的目标是使其融入并感觉到默认MVC Web API的“登录/注册”流程的组成部分,以确保其易于理解。
通过以下解决方案后,Authorize属性必须如下所示才能工作,否则您将获得Unauthorized响应。
[Authorize]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalBearer)]
[HostAuthentication(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie)]
使用ExternalBearer
,如果你希望只允许令牌使用API,使用ApplicationCookie
,如果你希望只允许登录cookie的使用API,即从一个网站。如果您要同时使用两者的API,请同时使用两者。
将此动作添加到 AccountController.cs
// POST api/Account/RegisterExternalToken
[OverrideAuthentication]
[AllowAnonymous]
[Route("RegisterExternalToken")]
public async Task<IHttpActionResult> RegisterExternalToken(RegisterExternalTokenBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ExternalLoginData externalLogin = await ExternalLoginData.FromToken(model.Provider, model.Token);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != model.Provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return InternalServerError();
}
ApplicationUser user = await UserManager.FindAsync(new UserLoginInfo(externalLogin.LoginProvider,
externalLogin.ProviderKey));
bool hasRegistered = user != null;
ClaimsIdentity identity = null;
IdentityResult result;
if (hasRegistered)
{
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
else
{
user = new ApplicationUser() { Id = Guid.NewGuid().ToString(), UserName = model.Email, Email = model.Email };
result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
var info = new ExternalLoginInfo()
{
DefaultUserName = model.Email,
Login = new UserLoginInfo(model.Provider, externalLogin.ProviderKey)
};
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
identity = await UserManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
IEnumerable<Claim> claims = externalLogin.GetClaims();
identity.AddClaims(claims);
Authentication.SignIn(identity);
}
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromDays(365));
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
// Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
JObject token = new JObject(
new JProperty("userName", user.UserName),
new JProperty("id", user.Id),
new JProperty("access_token", accessToken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", TimeSpan.FromDays(365).TotalSeconds.ToString()),
new JProperty(".issued", currentUtc.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'")),
new JProperty(".expires", currentUtc.Add(TimeSpan.FromDays(365)).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"))
);
return Ok(token);
}
将此辅助方法添加到ExternalLoginData
的辅助区域中的类中AccountController.cs
public static async Task<ExternalLoginData> FromToken(string provider, string accessToken)
{
string verifyTokenEndPoint = "", verifyAppEndpoint = "";
if (provider == "Facebook")
{
verifyTokenEndPoint = string.Format("https://graph.facebook.com/me?access_token={0}", accessToken);
verifyAppEndpoint = string.Format("https://graph.facebook.com/app?access_token={0}", accessToken);
}
else if (provider == "Google")
{
return null; // not implemented yet
//verifyTokenEndPoint = string.Format("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={0}", accessToken);
}
else
{
return null;
}
HttpClient client = new HttpClient();
Uri uri = new Uri(verifyTokenEndPoint);
HttpResponseMessage response = await client.GetAsync(uri);
ClaimsIdentity identity = null;
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
dynamic iObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
uri = new Uri(verifyAppEndpoint);
response = await client.GetAsync(uri);
content = await response.Content.ReadAsStringAsync();
dynamic appObj = (Newtonsoft.Json.Linq.JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(content);
identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
if (provider == "Facebook")
{
if (appObj["id"] != Startup.facebookAuthOptions.AppId)
{
return null;
}
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, iObj["id"].ToString(), ClaimValueTypes.String, "Facebook", "Facebook"));
}
else if (provider == "Google")
{
//not implemented yet
}
}
if (identity == null)
return null;
Claim providerKeyClaim = identity.FindFirst(ClaimTypes.NameIdentifier);
if (providerKeyClaim == null || String.IsNullOrEmpty(providerKeyClaim.Issuer) || String.IsNullOrEmpty(providerKeyClaim.Value))
return null;
if (providerKeyClaim.Issuer == ClaimsIdentity.DefaultIssuer)
return null;
return new ExternalLoginData
{
LoginProvider = providerKeyClaim.Issuer,
ProviderKey = providerKeyClaim.Value,
UserName = identity.FindFirstValue(ClaimTypes.Name)
};
}
最后RegisterExternalTokenBindingModel
是动作所使用的
public class RegisterExternalTokenBindingModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[Display(Name = "Token")]
public string Token { get; set; }
[Required]
[Display(Name = "Provider")]
public string Provider { get; set; }
}
是的,我们在注册时将电子邮件和令牌详细信息一起传递,这不会导致您在使用Twitter时更改代码,因为Twitter不向用户提供电子邮件。我们验证令牌来自我们的应用程序。一旦电子邮件注册,被黑客入侵或其他人的令牌无法用于更改电子邮件或获取该电子邮件的本地令牌,因为无论发送的电子邮件是什么,它将始终为所传递的社交令牌的实际用户返回本地令牌。
RegisterExternalToken
端点通过两种方式来获取令牌,即注册用户并发送本地令牌,或者如果用户已经注册,则发送令牌。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句