MVC和Web API在不同项目时如何存储不记名令牌

Posted

技术标签:

【中文标题】MVC和Web API在不同项目时如何存储不记名令牌【英文标题】:How to store bearer tokens when MVC and Web API are in different projects 【发布时间】:2016-04-12 23:56:54 【问题描述】:

情况: 我有一个 Web API 2 项目,它充当授权服务器(/token 端点)和资源服务器。我正在使用带有 ASP.Net Web API 的现成模板减去任何 MVC 引用。 Start.Auth 配置如下:

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.UseCookieAuthentication(new CookieAuthenticationOptions());
            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("/Account/ExternalLogin"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            ;

            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(OAuthOptions);

            var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
            
                AppId = ConfigurationManager.AppSettings["Test_Facebook_AppId"],
                AppSecret = ConfigurationManager.AppSettings["Test_Facebook_AppSecret"],
                //SendAppSecretProof = true,
                Provider = new FacebookAuthenticationProvider
                
                    OnAuthenticated = (context) =>
                    
                        context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
                        return Task.FromResult(0);
                    
                
            ;

            facebookAuthenticationOptions.Scope.Add("email user_about_me user_location");
            app.UseFacebookAuthentication(facebookAuthenticationOptions);

        

MVC 5 客户端(不同的项目)使用 Web API 应用程序进行授权和数据。以下是在用户名/密码存储的情况下检索承载令牌的代码:

[HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    
        if (!ModelState.IsValid)
        
            model.ExternalProviders = await GetExternalLogins(returnUrl);
            return View(model);
        

        var client = Client.GetClient();

        var response = await client.PostAsync("Token", 
            new StringContent(string.Format("grant_type=password&username=0&password=1", model.Email, model.Password), Encoding.UTF8));

        if (response.IsSuccessStatusCode)
        
            return RedirectToLocal(returnUrl);
        
        return View();
    

问题

我可以检索 Bearer 令牌,然后将其添加到 Authorization Header 以供后续调用。我认为在 Angular 应用程序或 SPA 的情况下可以。但我认为 MVC 中应该有一些东西可以为我处理它,比如自动将它存储在 cookie 中并在后续请求中发送 cookie。我已经搜索了很多,并且有一些帖子暗示了这一点(Registering Web API 2 external logins from multiple API clients with OWIN Identity),但我无法弄清楚在获得令牌后该怎么做。

是否需要在 MVC 应用 Startup.Auth 中添加一些内容?

理想情况下,我需要 ASP.Net 模板(MVC + Web API)中的 AccountController 提供开箱即用的功能(登录、注册、外部登录、忘记密码等),但使用 MVC 和 Web API在不同的项目中。

是否有包含此样板代码的模板或 git 存储库?

提前致谢!

更新 结合@FrancisDucharme 的建议,下面是GrantResourceOwnerCredentials() 的代码。

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            

            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
               OAuthDefaults.AuthenticationType);
            ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
                CookieAuthenticationDefaults.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(user.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);

            //Add a response cookie...
            context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));


            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        

但我似乎仍然无法获得该 Cookie 或弄清楚下一步该做什么。

重述问题:

    从 MVC 客户端进行身份验证、授权和调用 Web API 方法(身份验证和资源服务器)的正确方法是什么? 是否有 AccountController 的样板代码或模板来执行基本管道(登录、注册 - 内部/外部、忘记密码等)?

【问题讨论】:

如果您的 Web API 在响应 cookie 中返回令牌哈希,假设客户端浏览器启用了 cookie,客户端将为所有后续请求发回此 cookie。 @FrancisDucharme 请您详细说明此过程。我正在使用来自 web api 模板的标准令牌端点和配置。 您的主要问题是您希望 MVC 客户端始终自动添加 Authorization: Bearer &lt;hash&gt; 标头,对吗? 是的,我认为 MVC 方法是将其存储在一个 cookie 中,该 cookie 在后续请求中发送(我可能是非常错误的)。我确定我在 MVC 客户端的 Startup.Auth 中遗漏了一些东西。现在,我没有在客户端中配置身份验证。 获得令牌作为响应后,我是否需要做一些额外的事情(在 MVC 登录方法中 - 已在上面发布)? 【参考方案1】:

您可以让您的 Startup 类返回一个响应 cookie,然后客户端将在所有后续请求中返回该响应 cookie,这是一个示例。我会在GrantResourceOwnerCredentials 做。

public class AuthorizationServerProvider : OAuthAuthorizationServerProvider


    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    
        context.Validated();
    

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
                              

        //your authentication logic here, if it fails, do this...
        //context.SetError("invalid_grant", "The user name or password is incorrect.");
        //return;

         var identity = new ClaimsIdentity(context.Options.AuthenticationType);
         identity.AddClaim(new Claim("sub", context.UserName));
         identity.AddClaim(new Claim("role", "user"));

         AuthenticationTicket ticket = new AuthenticationTicket(identity);

        //Add a response cookie...
        context.Response.Cookies.Append("Token", context.Options.AccessTokenFormat.Protect(ticket));

        context.Validated(ticket);


启动类:

public partial class Startup


    public static OAuthBearerAuthenticationOptions OAuthBearerOptions  get; private set; 

    public Startup()
    
        OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
    

    public void Configuration(IAppBuilder app)
    
        HttpConfiguration config = new HttpConfiguration();

        ConfigureOAuth(app);
        //I use CORS in my projects....
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);

        WebApiConfig.Register(config);

    

    public void ConfigureOAuth(IAppBuilder app)
    
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        
            AllowInsecureHttp = true, //I have this here for testing purpose, production should always only accept HTTPS encrypted traffic.
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = new AuthorizationServerProvider()
        ;

        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(OAuthBearerOptions);

    

当然,这假设客户端启用了 cookie。

然后,modify your MVC headers 将 Authorization 标头添加到所有请求中。

ActionFilterAttribute 中,获取您的 cookie 值 (Token) 并添加标题。

【讨论】:

感谢@FrancisDucharme 的详细解释。我对oAuth有点陌生。让我吸收所有这些信息,一旦我把它用于我的设置,我就会回来:) 我已根据您的建议更新了问题。我无法在浏览器中获取该 cookie,可能是因为我使用的是 HttpClient(请原谅我对这个概念的有限理解)。你认为这是正确的方法吗?我更新了问题,以了解实现整个握手的正确方法。 @AmanvirSinghMundra 对不起,我对 ASP MVC 客户端没有太多经验。 Client 到底是什么?如果您在 POST 到 /token 时在响应标头中获得了 cookie,请检查 Chrome 网络。 ASP.Net MVC 5 是客户端应用程序。检查了 chrome 网络选项卡,cookie 没有显示出来!但是感谢您的帮助。我会挖掘更多。【参考方案2】:

我没有将其存储在会话中,而是将其添加到 DefaultRequestHeaders 中,如下所示,因此我不需要在每次调用 Web API 时都添加它。

    public async Task AuthenticateUser(string username, string password)
    
        var data = new FormUrlEncodedContent(new[]
        
            new KeyValuePair<string, string>("grant_type", "password"),
            new KeyValuePair<string, string>("username", username),
            new KeyValuePair<string, string>("password", password)
        );

        using (HttpResponseMessage response = await APIClient.PostAsync("/Token", data))
        
            if (response.IsSuccessStatusCode)
            
                var result = await response.Content.ReadAsAsync<AuthenticatedUser>();
                APIClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.Access_Token);
            
            else
            
                throw new Exception(response.ReasonPhrase);
            
        
    

【讨论】:

请将代码和数据添加为文本 (using code formatting),而不是图像。图片:A)不允许我们复制粘贴代码/错误/数据进行测试; B) 不允许根据代码/错误/数据内容进行搜索;和many more reasons。除了代码格式的文本之外,只有在图像添加了一些重要的东西,而不仅仅是文本代码/错误/数据传达的内容时,才应该使用图像。

以上是关于MVC和Web API在不同项目时如何存储不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

我如何在服务器端存储不记名令牌以及验证如何在 Web API 2 中注销时删除?

不记名令牌如何在 Web API 2 中存储在服务器端?

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

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

JWT 不记名令牌授权应用于 .NET Core 中的现有 MVC Web 应用程序

实施 Identity 2.1 + OWIN OAuth JWT 不记名令牌时如何从 Web API 控制器端点进行身份验证