【问题标题】:Where to store Bearer Token in MVC from Web APIWeb API 在 MVC 中存储承载令牌的位置
【发布时间】:2017-01-20 19:35:21
【问题描述】:

场景

我有一个 ASP.NET Web API,它使用 OAuth 密码流提供不记名令牌来访问其资源。

我现在正在制作一个需要使用此 API 的 MVC 应用程序。

计划是让 MVC 控制器代表客户端浏览器调用 API。

来自浏览器的 ajax 请求将到达 MVC 控制器,然后进行 API 调用。然后将结果作为 JSON 反馈给客户端,并在 java-script 中处理。

客户端不应直接与 API 通信。

进行身份验证。

一旦通过成功调用 Web api 令牌端点在 MVC 应用程序中收到承载令牌,我需要找到处理承载令牌的最佳方式。

我需要在对 api 的任何后续调用中使用此不记名令牌。

我的计划是将其存储在System.Web.HttpContext.Current.Session["BearerToken"]

然后我可以创建一个自定义AuthorizationAttribute,它将检查当前 HttpContext 中是否存在 BearerToken,如果不存在,客户端将需要重新访问令牌端点。

这看起来可行吗?

我正在征求人们对此的意见,因为我不相信这是我项目的最佳解决方案。

【问题讨论】:

  • 不记名令牌将进入您的请求标头。我相信您正在通过 AJAX 调用访问 Web API。因此,您必须使用从 Web API 收到的令牌适当地创建 AJAX 请求。我在这里做了类似的事情,但没有 AJAX-stackoverflow.com/questions/38661090/…
  • 我正在使用 MVC 应用程序中的 HttpClient 对象进行 wep api 调用

标签: c# security session asp.net-mvc-5 asp.net-web-api2


【解决方案1】:

我设法想出了一些我认为会很好用的东西。

我正在使用 Owin 中间件进行 Cookie 身份验证。

在 MVC 应用程序中,我有一个 Owin 启动文件,其中配置了 Cookie 身份验证:-

 public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888

            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationType = "ApplicationCookie",
                LoginPath = new PathString("/Account/Login"),

            });
        }
    }

然后我创建了一个 AccountController,其中包含两个用于登录和注销的操作方法:-

登录。

public ActionResult Login(LoginModel model,string returnUrl)
        {
            var getTokenUrl = string.Format(ApiEndPoints.AuthorisationTokenEndpoint.Post.Token, ConfigurationManager.AppSettings["ApiBaseUri"]);

            using (HttpClient httpClient = new HttpClient())
            {
                HttpContent content = new FormUrlEncodedContent(new[]
                {
                    new KeyValuePair<string, string>("grant_type", "password"), 
                    new KeyValuePair<string, string>("username", model.EmailAddress), 
                    new KeyValuePair<string, string>("password", model.Password)
                });

                HttpResponseMessage result = httpClient.PostAsync(getTokenUrl, content).Result;

                string resultContent = result.Content.ReadAsStringAsync().Result;

                var token = JsonConvert.DeserializeObject<Token>(resultContent);

                AuthenticationProperties options = new AuthenticationProperties();

                options.AllowRefresh = true;
                options.IsPersistent = true;
                options.ExpiresUtc = DateTime.UtcNow.AddSeconds(int.Parse(token.expires_in));

                var claims = new[]
                {
                    new Claim(ClaimTypes.Name, model.EmailAddress),
                    new Claim("AcessToken", string.Format("Bearer {0}", token.access_token)),
                };

                var identity = new ClaimsIdentity(claims, "ApplicationCookie");

                Request.GetOwinContext().Authentication.SignIn(options, identity);

            }

            return RedirectToAction("Index", "Home");
        }

退出

  public ActionResult LogOut()
            {
                Request.GetOwinContext().Authentication.SignOut("ApplicationCookie");

                return RedirectToAction("Login");
            }

保护资源

    [Authorize]
    public class HomeController : Controller
    {

        private readonly IUserSession _userSession;

        public HomeController(IUserSession userSession)
        {
            _userSession = userSession;
        }

        // GET: Home
        public ActionResult Index()
        {

            ViewBag.EmailAddress = _userSession.Username;
            ViewBag.AccessToken = _userSession.BearerToken;

            return View();
        }
    }


 public interface IUserSession
    {
        string Username { get; }
        string BearerToken { get; }
    }

public class UserSession : IUserSession
    {

        public string Username
        {
            get { return ((ClaimsPrincipal)HttpContext.Current.User).FindFirst(ClaimTypes.Name).Value; }
        }

        public string BearerToken
        {
            get { return ((ClaimsPrincipal)HttpContext.Current.User).FindFirst("AcessToken").Value; }
        }

    }

【讨论】:

  • 我的 HomeController/Index 上的 userSession 对象为 NULL
  • 如果您没有在应用程序中使用依赖注入,则 userSession 将为空。
  • @Derek,是的,你是对的,你能帮我解决这个问题吗?
  • 那是一个完全不同的话题。研究 Autofac 库。它易于实施!
  • 处理登录的好方法,我仍然想知道你是如何管理“过期的Access_token”的?我的理解是即使令牌过期,cookie 身份验证也会让您保持登录状态。下一次 API 调用,您将获得一个全新的令牌。你需要更换它。你会怎么处理?另外,如果 Access_token 将用户登录或注销设置为 MVC 应用程序范围怎么办?
【解决方案2】:

既然您提到您正在使用 HttpClient()。我使用 HttpClient()-

做了类似的事情

获取令牌-

    static Dictionary<string, string> GetTokenDetails(string userName, string password)
    {
        Dictionary<string, string> tokenDetails = null;
        try
        {
            using (var client = new HttpClient())
            {
                var login = new Dictionary<string, string>
                   {
                       {"grant_type", "password"},
                       {"username", userName},
                       {"password", password},
                   };

                var resp = client.PostAsync("http://localhost:61086/token", new FormUrlEncodedContent(login));
                resp.Wait(TimeSpan.FromSeconds(10));

                if (resp.IsCompleted)
                {
                    if (resp.Result.Content.ReadAsStringAsync().Result.Contains("access_token"))
                    {
                        tokenDetails = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp.Result.Content.ReadAsStringAsync().Result);
                    }
                }
            }
        }
        catch (Exception ex)
        {

        }
        return tokenDetails;
    }

使用令牌发布数据

static string PostData(string token, List<KeyValuePair<string, string>> lsPostContent)
{
    string response = String.Empty;
    try
    {
        using (var client = new HttpClient())
        {
            FormUrlEncodedContent cont = new FormUrlEncodedContent(lsPostContent);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var resp = client.PostAsync("https://localhost:61086/api/<your API controller>/", cont);

            resp.Wait(TimeSpan.FromSeconds(10));

            if (resp.IsCompleted)
            {
                if (resp.Result.StatusCode == HttpStatusCode.Unauthorized)
                {
                    Console.WriteLine("Authorization failed. Token expired or invalid.");
                }
                else
                {
                    response = resp.Result.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(response);
                }
            }
        }
    }
    catch (Exception ex)
    {

    }
    return response;
}

即使您将 Bearer 令牌存储在 HttpContext 中,您也需要注意在 Web API 中设置的令牌到期时间。仅在会话中验证令牌的存在无济于事,因为旧令牌将在过期时间后失效。

【讨论】:

    猜你喜欢
    • 2018-06-11
    • 2019-04-26
    • 2014-10-06
    • 2017-01-09
    • 2020-08-14
    • 2018-07-23
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    相关资源
    最近更新 更多