WebApi 上的 AngularJS 和 OWIN 身份验证

Posted

技术标签:

【中文标题】WebApi 上的 AngularJS 和 OWIN 身份验证【英文标题】:AngularJS and OWIN Authentication on WebApi 【发布时间】:2014-08-19 03:23:13 【问题描述】:

我已经在我的 WebApi 上实现了基于 OWIN 令牌的身份验证,我还通过调用 app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll) 启用了 CORS

我可以从 angularjs Web 客户端访问我的应用程序的各种不安全部分。我使用过 this http-interceptor ,当我尝试访问受保护的资源时,我会弹出登录信息。

现在为了登录,我必须使用表单编码的用户名密码和 grant_type 调用 http://mywebapi/token,请参阅下面的标题签名(来自 chrome)

Request URL:http://mywebapi/token
Request Headers CAUTION: Provisional headers are shown.
Accept:application/json, text/plain, */*
cache:false
Content-Type:application/x-www-form-urlencoded
Origin:http://127.0.0.1:49408
Referer:http://127.0.0.1:49408/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Form Dataview sourceview URL encoded
grant_type:password
UserName:correctuser
Password:Password

当我使用邮递员发送此请求时,它会以预期的访问令牌返回,但是当我尝试使用 Angular 的 $http 服务时,它会发出 OPTIONS 请求(我可以在开发工具控制台中看到)但是对于某些我收到此错误消息的原因

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:49408' is therefore not allowed access.

注意:这只发生在表单 URL 编码的 /token 请求中,对于所有其他 json 请求,标头按预期添加。有人可以帮忙吗,我的想法不多了。

谢谢

【问题讨论】:

【参考方案1】:

对于那些对答案和上一个答案感到好奇的人,它确实与排序密切相关。每当您添加 Owin 中间件时,请务必注意:注册顺序是必不可少的。

app.useCors(Microsoft.Owin.Cors.CorsOptions.AllowAll)

将此作为您的身份验证文件中的第一件事,基本上是在到达您的 OAuthServer 和 Web Api 之前注册 Cors 处理程序。

在 OAuth 之后移动它会产生相反的效果,因此需要在 GrantResourceOwnerCredentials 中添加 Access-Control-Allow-Origin 标头。

要回答另一个问题,如果您从浏览器发送 CORS 请求并且指定了 CorsOptions.AllowAll,则标头已经存在的原因,它会为您添加一个,因此当它到达 /token 端点时它已经添加了一个 OAuth 服务器。 (仅表示在 http 请求中找到了一个,并且您允许所有来源)。

您可以相应地验证行为,

在 Fiddler 中,使用包含任意值的 Origin 标头向您的 Token 端点发送一个新请求。在您的 OAuth 服务器上的 GrantResourceOwnerCredentials 中放置一个断点,然后检查 context.Response.Headers,它现在将包含您传入的来源。(请记住,浏览器必须检查它,fiddler 会高兴一整天)

如果您随后告诉 CORS 不要使用 CorsOptions.AllowAll 并将 AllowAnyOrigin 设置为 false,您会注意到从 Fiddler 发送的 Origin 不再添加到响应标头中。

浏览器反过来会拒绝 CORS 请求,因为没有返回源 - 在 Access-Control-Allow-Origin 标头中找不到源“”。

现在是重要的一点:

如果您设置 CorsOptions.AllowAll,它会完全按照它所说的去做,允许 CORS 请求在 Owin 管道中注册 CORS 之后发生的任何中间件上的任何方法,因此请确保这是您打算做的。 IE:如果您先注册 CORS,然后再注册 OAuth 和 Web API,那么如果您没有显式添加代码\属性来阻止它,那么您的所有 Web API 方法都可以通过 CORS 访问。

如果你想限制方法然后实现 ICorsPolicyProvider,部分来自http://katanaproject.codeplex.com/(Microsoft.Owin.Cors)

      public class MyCorsPolicyProvider : ICorsPolicyProvider
        
            public Task<CorsPolicy> GetCorsPolicyAsync(IOwinRequest request)
            
                // Grant Nothing.
                var policy = new CorsPolicy
                
                    AllowAnyHeader = false,
                    AllowAnyMethod = false,
                    AllowAnyOrigin = false,
                    SupportsCredentials = false
                ;

                // Now we can get a bit clever: (Awesome, they requested the token endpoint. Setup OAuth options for that.
                if (OAuthOptions.TokenEndpointPath.HasValue && OAuthOptions.TokenEndpointPath == request.Path)
                
                    // Hypothetical scenario, tokens can only be obtained using CORS when the Origin is http://localhost
                    policy.AllowAnyHeader = true;
                    policy.AllowAnyMethod = true;
                    policy.AllowAnyOrigin = false;
                    policy.SupportsCredentials = true;
                    policy.Origins.Add("http://localhost");

                return Task.FromResult(policy);
                
                // No token?, must already have one.... so this must be a WebApi request then.
                // From here we could check where the request is going, do some other fun stuff etc... etc...
                // Alternatively, do nothing, set config.EnableCors() in WebApi, then apply the EnableCors() attribute on your methods to allow it through.
return null;            
        

返回空值;告诉 Owin 继续到下一个中​​间件并允许请求通过但没有策略因此 NO CORS!,允许您在 WebAPI 中设置适当的 CORS 属性

现在非常重要的一点,如果 Access-Control-Allow-Origins 标头不存在,请不要将其添加到您的响应中,除非这确实是您想要的,因为这取决于您的中间件注册顺序,它将打开所有除非您在其他地方明确阻止它们或删除标头,否则 CORS 请求的大门,当您尝试将 CORS 与 WebApi 一起使用并想要限制它时,基本上会导致很多问题。

要在其他地方阻止它们,您可以为 WebApi 添加一个 CorsPolicyProvider (System.Web.Http),然后在 Owin 中设置一个 Context 变量,一旦请求到达 WebApi,您就可以读取该变量。

    public class WebApiCorsPolicyProvider : System.Web.Http.Cors.ICorsPolicyProvider
    
        public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        
            var policy = new CorsPolicy
            
                AllowAnyHeader = false,
                AllowAnyMethod = false,
                AllowAnyOrigin = false,
                SupportsCredentials = false
            ;
// The benefit of being at this point in the pipeline is we have been authenticated\authorized so can check all our claims for CORS purposes too if needed and set errors etc...

            // In an Owin pipeline?
            var owinContext = request.GetOwinContext();

            if (owinContext != null)
            
               // We have an owin pipeline, we can get owin parameters and other things here.
            
            else
            
                // Write your code here to determine the right CORS options. Non Owin pipeline variant.   
            

            return Task.FromResult(policy);
        
    

最后,向下传播到 WebApi CORS 策略提供程序的另一个好处是,此时将进行授权,因此您可以在该阶段在 CORS 策略提供程序中应用额外的 Origin 过滤器。

【讨论】:

【参考方案2】:

尽管我没有进一步调查,但我认为这与您的陈述顺序有关。我遇到了同样的问题并尝试了所有组合,最终对我有用。

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

        ConfigureOAuth(app);
        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.UseWebApi(config);
    

我在关注Token Based Authentication using ASP.NET Web API 2, Owin, and Identity

【讨论】:

【参考方案3】:

这是 Obi Onuorah 响应代码的另一个版本

        string corsHeader = "Access-Control-Allow-Origin";
        if (!context.Response.Headers.ContainsKey(corsHeader))
        
            context.Response.Headers.Add(corsHeader, new[]  "*" ); 
        

【讨论】:

【参考方案4】:

所以我找到了答案,但要振作起来,因为这很奇怪!!我阅读了this article on code project,这导致我使用我的 Owin 授权服务器的 GrantResourceOwnerCredentials 方法来检查这个

context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] "*" );

(我的是一个自定义的 Authoris(z)ation 服务器,我在 here 上偷了一个)

令我震惊的是它已经存在了!

所以我决定在那条线上设置一个断点,你知道吗,那条线失败了,因为(……更令人震惊)“Access-Control-Allow-Origin”已经在标题中了!!

所以我评论了那条线,这一切都奏效了!但是需要注意的是,我不知道标头是如何进入的,所以我不知道它是否会在生产中,所以我用它交换了那行代码来检查,然后如果它不存在就添加它

var header = context.OwinContext.Response.Headers.SingleOrDefault(h => h.Key == "Access-Control-Allow-Origin");
            if (header.Equals(default(KeyValuePair<string, string[]>)))
            
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[]  "*" );
            

我希望我的爱心劳动能拯救一些灵魂,使他们免于无数小时修补以解决这个问题的痛苦诅咒。干杯!

【讨论】:

我使用完全相同的源代码,但我仍然有错误。在我的情况下,标头不存在并且已成功添加,但错误仍然存​​在。 也为我修复了它。谢谢! @ObiOnuorah 我的身份验证提供程序基于同一篇文章,并且在看到您的答案之前放弃了解决方案!感激不尽! @Obi Onuorah 这是一篇旧帖子,我有一个问题,什么时候添加标题?无论我执行什么添加标题,我的代码都没有命中。所以说删除这条线是安全的吗? 对不起@Katana 我不明白你的问题【参考方案5】:

我经历了与大多数人相同的过程并花费(浪费?)相同的时间来处理 owin + web api。

对我有用的解决方案是搬家

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

在管道中的所有其他内容之前。

这里有一些代码:

OwinStartup

[assembly: OwinStartup(typeof(MyApp.Web.Startup))]
namespace MyApp.Web

    using Owin;
    using Microsoft.Owin;

    public partial class Startup
    
        public void Configuration(IAppBuilder app)
        
        var config = new System.Web.Http.HttpConfiguration();
        ConfigureAuth(app, config);
        
    

OAuth 启动

public partial class Startup

    public void ConfigureAuth(IAppBuilder app, System.Web.Http.HttpConfiguration config)
        
        // app.UseWelcomePage("/");
        // app.UseErrorPage();

        // Must be the first to be set otherwise it won't work.
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        app.CreatePerOwinContext<ApplicationDatabaseContext>(ApplicationDatabaseContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

        var OAuthOptions = new OAuthAuthorizationServerOptions
        
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new DaufAuthorizationServerProvider(),
            RefreshTokenProvider = new SimpleAuthorizationServerProvider(),
        ;
        app.UseOAuthAuthorizationServer(OAuthOptions);

        app.UseWebApi(WebApiConfig.Register(config, logger));
        

网络接口

public static class WebApiConfig

    public static HttpConfiguration Register(System.Web.Http.HttpConfiguration config, ILogger logger)
        
            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            // This will used the HTTP header: "Authorization"      Value: "Bearer 1234123412341234asdfasdfasdfasdf"
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/controller/id",
                defaults: new  id = RouteParameter.Optional 
            );

            return (config);
        

【讨论】:

将 app.UseCors 移到顶部也对我有用。我不知道为什么所有示例都显示它在管道的后期设置。谢谢你的回答。 不客气。我想新版本已经解决了问题,但我不是 100% 确定。干杯。 对我来说,这是正确的答案!我开始了一天,对 CORS 和 OWIN 一无所知,并不是我觉得我知道的比我想知道的要多得多。感谢您的好评。 是的,我知道这是正确的答案 ;-) 我苦苦挣扎了几天才弄清楚整个事情是如何运作的。 我还必须安装 Microsoft.Owin.Cors nuget 包

以上是关于WebApi 上的 AngularJS 和 OWIN 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET WebAPI2 和 AngularJS

使用 webApi 和 angularjs 的间接下载链接

webAPI+angularJS文件上传和下载

将多个值从 WebApi(post)返回到 AngularJs

从 angularjs 发布到 WebAPI 时出现问题

angularjs+webapi2 跨域Basic 认证授权