将身份验证同时用于 MVC 页面和 Web API 页面?

Posted

技术标签:

【中文标题】将身份验证同时用于 MVC 页面和 Web API 页面?【英文标题】:Combine the use of authentication both for MVC pages and for Web API pages? 【发布时间】:2015-11-16 15:28:16 【问题描述】:

我有一个 MVC 5 Web 应用程序,可以使用 Login.cshtml 页面登录并获取 cookie,并且登录工作正常。但是,我想使用 Web API 登录,然后(也许)设置一个 cookie,以便我登录我的 MVC 页面......(或使用 MVC 登录然后访问 Web API)但是web api返回一个不记名令牌而不是cookie令牌......所以这不起作用。有没有一种方法可以为我的 MVC 页面和我的 Web API 页面结合使用身份验证?

更新:

这不是真正的代码问题,更多的是概念问题。

普通 MVC 网页会检查一个名为“.AspNet.ApplicationCookie”的 cookie,默认情况下,以确定请求者的身份。此 cookie 是通过调用 ApplicationSignInManager.PasswordSignInAsync 生成的。

另一方面,WebAPI 调用会检查名为 Authorization... 的项目的请求标头,并使用该值来确定请求者的身份。这是从对“/Token”的 WebAPI 调用返回的。

这些是非常不同的值。我的网站需要同时使用 MVC 页面 WebAPI 调用(以动态更新这些页面)...并且两者都需要经过身份验证才能执行其任务。

我能想到的唯一方法是实际进行两次身份验证...一次使用 WebAPI 调用,另一次使用登录帖子。 (见下面我的回答)。

这似乎很hacky...但我对授权代码的了解不够,无法知道是否有更合适的方法来完成此操作。

【问题讨论】:

请出示代码 【参考方案1】:

实现这一点的最佳方法是在您的 MVC 项目中拥有一个授权服务器(一个生成令牌的 Web API)和令牌消费中间件。 IdentityServer 应该会有所帮助。但是我已经这样做了:

按照here 的说明,我使用 JWT 和 Web API 和 ASP.Net Identity 构建了一个授权服务器。

完成此操作后,您的 Web API startup.cs 将如下所示:

 // Configures cookie auth for web apps and JWT for SPA,Mobile apps
 private void ConfigureOAuthTokenGeneration(IAppBuilder app)
 
    // Configure the db context, user manager and role manager to use a single instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

    // Cookie for old school MVC application
    var cookieOptions = new CookieAuthenticationOptions
    
        AuthenticationMode = AuthenticationMode.Active,
        CookieHttpOnly = true, // javascript should use the Bearer
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,                
        LoginPath = new PathString("/api/Account/Login"),
        CookieName = "AuthCookie"
    ;
    // Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
    app.UseCookieAuthentication(cookieOptions);

    OAuthServerOptions = new OAuthAuthorizationServerOptions()
    
        //For Dev enviroment only (on production should be AllowInsecureHttp = false)
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/oauth/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
        Provider = new CustomOAuthProvider(),                
        AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
    ;

    // OAuth 2.0 Bearer Access Token Generation
    app.UseOAuthAuthorizationServer(OAuthServerOptions);

您可以找到CustomOAuthProviderCustomJwtFormat 类here。

我在我想要使用相同令牌保护的所有其他 API(资源服务器)中编写了一个消费逻辑(即中间件)。由于您想在 MVC 项目中使用 Web API 生成的令牌,因此在实现授权服务器后,您需要执行以下操作:

在您的 MVC 应用程序中,将其添加到 startup.cs:

public void Configuration(IAppBuilder app)

        ConfigureOAuthTokenConsumption(app);


private void ConfigureOAuthTokenConsumption(IAppBuilder app)

    var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
    string audienceid = ConfigurationManager.AppSettings["AudienceId"];
    byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);

    app.UseCookieAuthentication(new CookieAuthenticationOptions  CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie );

    //// Api controllers with an [Authorize] attribute will be validated with JWT
    app.UseJwtBearerAuthentication(
        new JwtBearerAuthenticationOptions
        
            AuthenticationMode = AuthenticationMode.Passive,
            AuthenticationType = "JWT",
            AllowedAudiences = new[]  audienceid ,
            IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
            
                new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)                           
            

        );

在您的 MVC 控制器中,当您收到令牌时,将其反序列化并从访问令牌生成 cookie:

AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;               
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);

生成机器密钥并将其添加到您的 Web API 和 ASP.Net MVC 站点的 web.config

这样,将创建一个 cookie,并且 MVC 站点中的 [Authorize] 属性和 Web API 将使用此 cookie。

PS 我已经通过发布 JWT(授权服务器或身份验证和资源服务器)的 Web API 完成了此操作,并且能够在 ASP.Net MVC 网站、Angular 内置的 SPA 站点中使用它,在 python(资源服务器)、spring(资源服务器)和 android 应用程序中内置的安全 API。

【讨论】:

我认为有一个错字:app.UseCookieAuthentication(new CookieAuthenticationOptions()); 应该是私有方法 ConfigureOAuthTokenGeneration 中的app.UseCookieAuthentication(cookieOptions); 是否可以使用外部身份验证提供程序对 Microsoft Azure AD 进行身份验证(例如,调用 app.UseWindowsAzureActiveDirectoryBearerAuthentication)?【参考方案2】:

Ugg...我必须做的是使用 Login.cshtml 表单并覆盖提交...进行 Ajax 调用以获取 WebApi 不记名令牌...然后执行表单提交以获取实际的 MVC曲奇饼。所以,我实际上发出了两个登录请求……一个用于 WebApi 令牌,另一个用于 MVC cookie。

对我来说似乎很 hacky...如果有某种方法可以使用不记名令牌登录到 MVC...或者调用 WebApi 会返回我可以正常使用的 cookie MVC 页面请求。

如果有人有更好的方法,我很想听听。

这是我添加到 Login.cshtml 中的脚本代码:

    $(document).ready(function () 
        $('form:first').submit(function (e) 
            e.preventDefault();
            var $form = $(this);
            var formData = $form.serializeObject(); // https://github.com/macek/jquery-serialize-object
            formData.grant_type = "password";
            $.ajax(
                type: "POST",
                url: '@Url.Content("~/Token")',
                dataType: "json",
                data: formData, // seems like the data must be in json format
                success: function (data) 
                    sessionStorage.setItem('token', data.access_token);
                    $form.get(0).submit(); // do the actual page post now
                ,
                error: function (textStatus, errorThrown) 
                
            );
        );
    );

【讨论】:

【参考方案3】:

我假设您尝试做的是让 MVC 提供的页面具有调用 Web API 方法的 javascript。如果您使用 ASP.NET Identity 来处理身份验证(看起来您正在这样做),那么 MVC 应该使用可以传递给 Web API 进行身份验证的 OAuth 令牌。

这是一些 JavaScript 代码的 sn-p,它在类似情况下对我有用:

var token = sessionStorage.getItem('access_token');
var headers = ;
if (token) 
    headers.Authorization = 'Bearer ' + token;

$.ajax(
    type: <GET/POSt/...>,
    url: <your api>,
    headers: headers
).done(function (result, textStatus) 

【讨论】:

你的描述完全正确......但问题是你如何以及在哪里获得 access_token,首先存储在 sessionStorage 中......以及你在哪里以及如何获得用于 MVC 页面身份验证的 cookie。 访问令牌由 MVC 框架存储在 sessionStorage 中,我的代码不必将其存储在那里。如果您的代码没有发生这种情况,请发布您如何配置 ASP.NET Identity/OAuth。【参考方案4】:

我和你有类似的情况,但我使用不同的方式进行身份验证。

我有一个 web 和一个 api,它们都适用于 Intranet 用户。我不使用用户的身份来传递 web 和 api。相反,我创建了一个单独的 web 帐户,每次 web 都会使用这个特殊帐户连接到 api。

因为,我们还需要确保用户不应该直接连接到 api。他们应该只连接到 web ui。

希望对您有所帮助。

【讨论】:

【参考方案5】:

从上面的 cmets 中,据我了解,您有一个场景,您通过浏览器执行登录,但还必须使用 ajax 调用调用 web-api 方法。

浏览器调用是基于会话 cookie 的。虽然来自浏览器的 ajax 调用将在标头中包含会话 cookie,但需要存在身份验证标头以供 web-api 执行验证。

因此,在成功登录时,您还必须生成一个基于 web-api 的令牌,将其设置为 cookie(可通过 javascript 访问),然后在进行 ajax 调用时,从 cookie 中获取并包含它作为“授权”标题中的标题。

【讨论】:

以上是关于将身份验证同时用于 MVC 页面和 Web API 页面?的主要内容,如果未能解决你的问题,请参考以下文章

身份验证/授权 MVC 5 和 Web API - Katana/Owin

Laravel Api 和 Web 身份验证同时进行

同时对 Asp.Net core 2.0 MVC 应用程序 (cookie) 和 REST API (JWT) 进行身份验证

如何使用 Web API 来对 MVC 应用程序进行身份验证

如何使用 Web API 来对 MVC 应用程序进行身份验证

AngularJS + ASP.NET Web API + ASP.NET MVC 身份验证