WebApi ASP.NET Identity Facebook登录

 棉布缺嘴_621 发布于 2023-02-03 11:36

在asp.net身份的facebook身份验证流程中,facebook oauth对话框将代码而不是访问令牌附加到redirect_url,以便服务器可以通过此代码交换此代码http://localhost:49164/signin-facebook?code=...&state=....

我的问题是我的客户端是一个使用facebook sdk的移动应用程序,它直接给了我一个访问令牌.Facebook说使用sdk总是给你一个访问令牌,所以我可以直接给web api访问令牌吗?

我知道这不是很安全,但它是否可能?

3 个回答
  • 随后来自@ s0nica的出色解决方案,我修改了一些代码,以便与当前实现的ASP.NET MVC模板集成.s0nica方法很好,但与MVC(非WebApi)不完全兼容AccountController.

    我的方法的好处是使用ASP.NET MVC和WebApi,反之亦然.

    主要区别在于索赔名称.FacebookAccessToken使用声明名称后跟链接(http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the -vs-2013-project-templates.aspx),我的方法与给定链接的方法兼容.我建议使用它.

    请注意,以下代码是@ s0nica的答案的修改版本.所以,(1)演练给出链接,(2)然后再演练s0nica的代码,(3)最后考虑我的.

    Startup.Auth.cs文件.

    public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
        {
            // This validates the identity based on the issuer of the claim.
            // The issuer is set in the API endpoint that logs the user in
            public override Task ValidateIdentity(OAuthValidateIdentityContext context)
            {
                var claims = context.Ticket.Identity.Claims;
                if (!claims.Any() || claims.Any(claim => claim.Type != "FacebookAccessToken")) // modify claim name
                    context.Rejected();
                return Task.FromResult<object>(null);
            }
        }
    

    API/AccountController.cs

            // POST api/Account/FacebookLogin
        [HttpPost]
        [AllowAnonymous]
        [Route("FacebookLogin")]
        public async Task<IHttpActionResult> FacebookLogin([FromBody] FacebookLoginModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
    
            if (string.IsNullOrEmpty(model.token))
            {
                return BadRequest("No access token");
            }
    
            var tokenExpirationTimeSpan = TimeSpan.FromDays(300);
            ApplicationUser user = null;
            string username;
            // Get the fb access token and make a graph call to the /me endpoint
            var fbUser = await VerifyFacebookAccessToken(model.token);
            if (fbUser == null)
            {
                return BadRequest("Invalid OAuth access token");
            }
    
            UserLoginInfo loginInfo = new UserLoginInfo("Facebook", model.userid);
            user = await UserManager.FindAsync(loginInfo);
    
            // If user not found, register him with username.
            if (user == null)
            {
                if (String.IsNullOrEmpty(model.username))
                    return BadRequest("unregistered user");
    
                user = new ApplicationUser { UserName = model.username };
    
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, loginInfo);
                    username = model.username;
                    if (!result.Succeeded)
                        return BadRequest("cannot add facebook login");
                }
                else
                {
                    return BadRequest("cannot create user");
                }
            }
            else
            {
                // existed user.
                username = user.UserName;
            }
    
            // common process: Facebook claims update, Login token generation
            user = await UserManager.FindByNameAsync(username);
    
            // Optional: make email address confirmed when user is logged in from Facebook.
            user.Email = fbUser.email;
            user.EmailConfirmed = true;
            await UserManager.UpdateAsync(user);
    
            // Sign-in the user using the OWIN flow
            var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
    
            var claims = await UserManager.GetClaimsAsync(user.Id);
            var newClaim = new Claim("FacebookAccessToken", model.token); // For compatibility with ASP.NET MVC AccountController
            var oldClaim = claims.FirstOrDefault(c => c.Type.Equals("FacebookAccessToken"));
            if (oldClaim == null)
            {
                var claimResult = await UserManager.AddClaimAsync(user.Id, newClaim);
                if (!claimResult.Succeeded)
                    return BadRequest("cannot add claims");
            }
            else
            {
                await UserManager.RemoveClaimAsync(user.Id, oldClaim);
                await UserManager.AddClaimAsync(user.Id, newClaim);
            }
    
            AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
            var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
            properties.IssuedUtc = currentUtc;
            properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
            AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
            var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
            Request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accesstoken);
            Authentication.SignIn(identity);
    
            // Create the response building a JSON object that mimics exactly the one issued by the default /Token endpoint
            JObject blob = new JObject(
                new JProperty("userName", user.UserName),
                new JProperty("access_token", accesstoken),
                new JProperty("token_type", "bearer"),
                new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
                new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
                new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString()),
                new JProperty("model.token", model.token),
            );
            // Return OK
            return Ok(blob);
        }
    

    用于绑定的Facebook登录模型(api/AccountController.cs的内部类)

        public class FacebookLoginModel
        {
            public string token { get; set; }
            public string username { get; set; }
            public string userid { get; set; }
        }
    
        public class FacebookUserViewModel
        {
            public string id { get; set; }
            public string first_name { get; set; }
            public string last_name { get; set; }
            public string username { get; set; }
            public string email { get; set; }
        }
    

    VerifyFacebookAccessToken方法(在api/AccountController.cs中)

        private async Task<FacebookUserViewModel> VerifyFacebookAccessToken(string accessToken)
        {
            FacebookUserViewModel fbUser = null;
            var path = "https://graph.facebook.com/me?access_token=" + accessToken;
            var client = new HttpClient();
            var uri = new Uri(path);
            var response = await client.GetAsync(uri);
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync();
                fbUser = Newtonsoft.Json.JsonConvert.DeserializeObject<FacebookUserViewModel>(content);
            }
            return fbUser;
        }
    

    2023-02-03 11:39 回答
  • 我不知道你是否终于找到了一个解决方案,但我正在尝试做一些非常相似的事情,我仍然把拼图的各个部分放在一起.我试图将此作为评论而不是答案发布,因为我没有提供真正的解决方案,但它太长了.

    显然,所有WebAPI Owin OAuth选项都是基于浏览器的,即它们需要大量不适合原生移动应用程序的浏览器重定向请求(我的情况).我还在调查和试验,但正如Hongye Sun在其博客文章的一篇评论中所简要描述的那样,http://blogs.msdn.com/b/webdev/archive/2013/09/20/understanding-security -features-in-spa-template.aspx?PageIndex = 2#comments,要使用Facebook登录,使用Facebook SDK收到的访问令牌可以通过API直接验证到/ me端点的图形调用.

    通过使用图形调用返回的信息,您可以检查用户是否已注册.最后,我们需要登录用户,可能使用Authentication.SignIn Owin方法,返回将用于所有后续API调用的承载令牌.

    编辑:其实我弄错了,在调用"/ Token"端点时发出了不记名令牌,在输入上接受类似 grant_type=password&username=Alice&password=password123 问题这里的问题是我们没有密码(这是OAuth机制的全部要点),所以我们怎样才能调用"/ Token"端点?

    更新:我终于找到了一个可行的解决方案,以下是我必须添加到现有类以使其工作的内容:Startup.Auth.cs

    public partial class Startup
    {
        /// <summary>
        /// This part has been added to have an API endpoint to authenticate users that accept a Facebook access token
        /// </summary>
        static Startup()
        {
            PublicClientId = "self";
    
            //UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
            UserManagerFactory = () => 
            {
                var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
                userManager.UserValidator = new UserValidator<ApplicationUser>(userManager) { AllowOnlyAlphanumericUserNames = false };
                return userManager;
            };
    
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                AllowInsecureHttp = true
            };
    
            OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
            OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
            OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
            OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
            OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
            OAuthBearerOptions.Description = OAuthOptions.Description;
            OAuthBearerOptions.Provider = new CustomBearerAuthenticationProvider();            
            OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock;
        }
    
        public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
    
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    
        public static Func<UserManager<ApplicationUser>> UserManagerFactory { get; 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)
        {
            [Initial boilerplate code]
    
            OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, OAuthBearerOptions);
    
            [More boilerplate code]
        }
    }
    
    public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
    {
        public override Task ValidateIdentity(OAuthValidateIdentityContext context)
        {
            var claims = context.Ticket.Identity.Claims;
            if (claims.Count() == 0 || claims.Any(claim => claim.Issuer != "Facebook" && claim.Issuer != "LOCAL_AUTHORITY" ))
                context.Rejected();
            return Task.FromResult<object>(null);
        }
    }
    

    进入AccountController我添加了以下操作

            [HttpPost]
            [AllowAnonymous]
            [Route("FacebookLogin")]
            public async Task<IHttpActionResult> FacebookLogin(string token)
            {
                [Code to validate input...]
                var tokenExpirationTimeSpan = TimeSpan.FromDays(14);            
                ApplicationUser user = null;    
                // Get the fb access token and make a graph call to the /me endpoint    
                // Check if the user is already registered
                // If yes retrieve the user 
                // If not, register it  
                // Finally sign-in the user: this is the key part of the code that creates the bearer token and authenticate the user
                var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, "Facebook"));
                    // This claim is used to correctly populate user id
                    identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY"));
                AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());            
                var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
                ticket.Properties.IssuedUtc = currentUtc;
                ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);            
                var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket); 
                Authentication.SignIn(identity);
    
                // Create the response
                JObject blob = new JObject(
                    new JProperty("userName", user.UserName),
                    new JProperty("access_token", accesstoken),
                    new JProperty("token_type", "bearer"),
                    new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
                    new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
                    new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
                );
                var json = Newtonsoft.Json.JsonConvert.SerializeObject(blob);
                // Return OK
                return Ok(blob);
            }
    

    而已.我在经典/令牌端点响应中发现的唯一区别是,承载令牌略短,过期和发布日期为UTC,而不是GMT(至少在我的机器上).

    我希望这有帮助!

    2023-02-03 11:39 回答
  • 是的,您可以使用外部访问令牌来安全登录.

    我强烈建议您按照本教程进行操作,该教程向您展示如何从头开始使用Web API 2进行基于令牌的身份验证(使用Angular JS作为前端).特别是,第4步包括两种方法,允许您使用外部访问令牌进行身份验证,例如从本机SDK返回:

    [AllowAnonymous, HttpGet]
    async Task<IHttpActionResult> ObtainLocalAccessToken(string provider, string externalAccessToken)
    
    [AllowAnonymous, HttpPost]
    async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
    

    简而言之:

      使用本机SDK获取外部访问令牌.

      调用ObtainLocalAccessToken("Facebook", "[fb-access-token]")以确定用户是否已拥有帐户(200响应),在这种情况下,将为您生成新的本地令牌.它还验证外部访问令牌是否合法.

      如果步骤2中的呼叫失败(400响应),则需要通过调用RegisterExternal,传递外部令牌来注册新帐户.上面的教程有一个很好的例子(参见associateController.js).

    2023-02-03 11:41 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有