Web API 在 MVC 中存储承载令牌的位置

Posted

技术标签:

【中文标题】Web API 在 MVC 中存储承载令牌的位置【英文标题】:Where to store Bearer Token in MVC from Web API 【发布时间】: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-***.com/questions/38661090/… 我正在使用 MVC 应用程序中的 HttpClient 对象进行 wep api 调用 【参考方案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 中设置的令牌到期时间。仅在会话中验证令牌的存在无济于事,因为旧令牌将在过期时间后失效。

【讨论】:

以上是关于Web API 在 MVC 中存储承载令牌的位置的主要内容,如果未能解决你的问题,请参考以下文章

了解 ASP.NET(MVC、Web API)中的令牌生成/验证

在 .net 核心 web api 中存储 JWT 令牌的位置?

Web Api 2 Preflight CORS 请求承载令牌

Web Api中的JSON Web令牌与承载令牌

在 ASP.NET Core 2.1 Web 客户端中存储不记名令牌的位置

使用 asp.net web api 令牌在 mvc 网站上进行身份验证