【问题标题】:Angular - WebAPI2 OWIN Authentication returning 401Angular - WebAPI2 OWIN 身份验证返回 401
【发布时间】:2016-10-02 06:03:18
【问题描述】:

我有一个以前没有遇到过的有趣问题。

我的前端和我的 api 项目在不同的来源上运行(因此需要 CORS)。我的应用程序会将用户名和密码发送到 OWIN 中间件以进行验证并返回令牌。它确实在验证后返回一个令牌。

奇怪的是,我从那里到具有 [Authorize] 属性的任何 WebAPI 端点的所有请求都返回 401 错误。

对于 Angular 请求,我有一个授权拦截器,它为每个请求添加“Bearer”授权标头。我已经根据登录期间返回的令牌检查了该标头中的令牌,并且它们匹配。

这几天我一直在用头撞墙,我想我只需要把它放在那里,希望有人能指出一些对我来说很明显的事情。

【问题讨论】:

  • 您正在使用identityServer3?
  • 你有没有把 $httpProvider.defaults.withCredentials = true;在你的 app.js 配置中?
  • @Xnake 是的,我做到了。我想我弄清楚了这个问题并在下面回答了自己。我可能会提出一个更详细的答案,因为我已经看到这样的问题被问过很多次了。

标签: angularjs asp.net-web-api2 owin


【解决方案1】:

在我们的应用程序中,我们在 login.aspx 页面中嵌入了身份验证。 特别是我们在 login.aspx.cs 中使用了这个机制,通过 cookie 身份验证

                IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
                ClaimsIdentity userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
                user.LastLoginDate = DateTime.Now;
                userManager.Update(user);

                authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, userIdentity);
                Response.Redirect("~/YourPage/");

【讨论】:

    【解决方案2】:

    我在其他 SO 问题中看到了很多此类问题,因此,除了解决我的问题(在本文末尾)之外,我还添加了如何设置 CORS Api 项目,该项目利用:使用 JWT 不记名身份验证和 .NET 身份授权的前端/.net web api。 这绝不是所有超级安全的做事方式的终结,但它会让你开始:

    1) 在 Web API 方面,我通过以下方式装饰类来设置我的 OWIN 启动类:

    [assembly: OwinStartup(typeof(OwinTestApp.Api.App_Start.Startup))]
    

    2) 然后我通过 ConfigureOAuthTokenGeneration、ConfigureOAuthTokenConsumption、ConfigureWebApi 配置服务器,然后告诉应用程序使用 WebAPI 使用 WebApi 的配置。

     public void Configuration(IAppBuilder app)
        {
    
            HttpConfiguration httpConfig = new HttpConfiguration();
    
            ConfigureOAuthTokenGeneration(app);
    
            ConfigureOAuthTokenConsumption(app);
    
            ConfigureWebApi(httpConfig);
    
            app.UseWebApi(httpConfig);
    
        }
    
        private void ConfigureOAuthTokenConsumption(IAppBuilder app)
        {
    
            var issuer = "OwinTestApp";
    
    //WebAPI server is serving as Resource and Authorization Server at the same time, so we are fixing the Audience Id and Audience Secret (Resource Server) in the web.config. If you separated your resource and authorization servers, then you would need to handle this differently
    
    
            string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
            byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
    
            app.UseJwtBearerAuthentication(
                new JwtBearerAuthenticationOptions
                {
                    AuthenticationMode = AuthenticationMode.Active,
                    AllowedAudiences = new[] { audienceId },
                    IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                    {
                        new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                    }
                });
        }
    
        private void ConfigureOAuthTokenGeneration(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);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    
            OAuthAuthorizationServerOptions 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 AuthorizationServerProvider(),
                AccessTokenFormat = new CustomJwtFormat("OwinTestApp")
            };
    
            // OAuth 2.0 Bearer Access Token Generation
            app.UseOAuthAuthorizationServer(OAuthServerOptions);
        }
    
    
        private void ConfigureWebApi(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
    
            var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
            jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();            
        }
    

    3) 这里的关键之一是在 OAuthServerOptions 中配置提供程序。我在这个主题上看到的大多数 SO 问题以及我在这个主题上看到的大多数教程都谈到了使用 Allowing all origins (app.UseCors(CorsOptions.AllowAll))。不要这样做。这对开发来说很好,但您很可能希望锁定您的请求的来源。当你在 OAuthServerOptions 中设置提供者时,你会这样做。

      /// <summary>
    /// To configure the Authorization server, you need to inherit OAuthAuthorizationServerProvider and override
    /// certain methods of it to handle request authorizatio
    /// </summary>
    public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        /// <summary>
        /// I'm validating the context here because I'm actually checking the user's credentials in the 
        /// GrantResourceOwnerCredentials method.  You can do a basic authentication check in this method
        /// if you're using a TryGetBasicCredentials with  client_id/client_secret properties
        /// </summary>
        /// <param name="context">The context of the event carries information in and results out.</param>
        /// <returns></returns>
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }
    
        /// <summary>
        /// I'm using ASP.NET identity to authenticate my user so I am doing the actual grant in this method
        /// </summary>
        /// <param name="context">The context of the event carries information in and results out.</param>
        /// <returns></returns>
        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;
            }
    
            if (!user.EmailConfirmed)
            {
                context.SetError("invalid_grant", "User did not confirm email.");
                return;
            }
    
            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
    
            //add static claims for the user
            oAuthIdentity.AddClaims(ExtendedClaimsProvider.GetClaims(user));
            //add any extra claims that may be added after the static claims have been added
            oAuthIdentity.AddClaims(RolesFromClaims.CreateRolesBasedOnClaims(oAuthIdentity));
    
            var ticket = new AuthenticationTicket(oAuthIdentity, null);
    
            context.Validated(ticket);
    
        }
    
        /// <summary>
        /// This is a key point to CORS.  CORS requests means that there is a pre-flight check (using an OPTIONS action)
        /// to see if the server allows access to this endpoint.
        /// </summary>
        /// <param name="context">Details of the request context</param>
        /// <returns></returns>
        public override Task MatchEndpoint(OAuthMatchEndpointContext context)
        {
            SetAllowedOrigins(context.OwinContext);
            //if this is a pre-flight request then indicate that the request completed and then
            //  return anything to indicate that the origin has access to this resource
            if (context.Request.Method == "OPTIONS")
            {
                context.RequestCompleted();
                return Task.FromResult(0);
            }
    
            //if its not a pre-flight request, then perform regular actions to match the endpoint 
            //  and authorization
            return base.MatchEndpoint(context);
        }
    
    
        /// <summary>
        /// add the allow-origin header only if the origin domain is found on the     
        /// allowedOrigin list
        /// </summary>
        /// <param name="context"></param>
        private void SetAllowedOrigins(IOwinContext context)
        {
    
            using (var db = new Data.Authorization.AuthDataContext()) {
                //origin gets the Origin of the request
                string origin = context.Request.Headers.Get("Origin");
                //check to see if the origin of the request is in your approved list of origins
                var allowedOrigin = db.OriginList.SingleOrDefault(a => a.Allowed && a.Active && a.Origin == origin);
                //if it is then add the Access-Control-Allow-Origin to your Response Header
                if (allowedOrigin != null) {
                    context.Response.Headers.Add("Access-Control-Allow-Origin", new string[] { origin });
                }
            }
            //if this is an OPTIONS action request then add the "Access-Control-Allow-Headers" && "Access-Control-Allow-Methods"
            //      these are necessary headers to receive on the pre-flight request to validate access to the resource and also what 
            //      actions the user can make.  This occurs PRE execution of any method.
            if (context.Request.Method == "OPTIONS")
            {
                context.Response.Headers.Add("Access-Control-Allow-Headers", new string[] { "Authorization", "Content-Type", "Cache-Control" });
                context.Response.Headers.Add("Access-Control-Allow-Methods", new string[] { "OPTIONS", "GET", "POST" });
    
            }
            //add this to allow the user to send credentials (and log in)
            context.Response.Headers.Add("Access-Control-Allow-Credentials", new string[] { "true" });
        }
    }
    

    最后,要设置客户端,您需要执行以下操作: 1) 将 $http 提供程序设置为随请求发送凭据:

      //need this for login to work.  token receipt wont work without this on there.  
    $httpProvider.defaults.withCredentials = true;
    

    2) 创建一个 http 拦截器,将 JWT 不记名令牌添加到每个请求中:

    //handle the request
        function _request(config) {
            //grab the current headers of the request
            config.headers = config.headers || {};
    
            //get the token that was sent by the authorization process
            var token = //<Get token from wherever you saved it>;
    
            //if the token is not null then add an Authorization header 
            //  and set it's value to the token and suffix it with Bearer
            if (token) {
                config.headers.Authorization = 'Bearer ' + token;
            }
    
            return config;
        }
    

    3) 最后,将您创建的 http 拦截器添加到 $http 提供者的拦截器列表中:

     //used to intercept calls and inject token after authorization has taken place
    $httpProvider.interceptors.push('authInterceptorService');
    

    我最初从bitoftech site 学到了很多这方面的知识。他在教程中对 CORS 使用了 allow all ,除非您真的希望允许从所有来源访问您的 API,否则您在任何情况下都不应该这样做。

    我遇到的问题实际上是在我的 ConfigureWebAPI 方法中解决的。我忘记映射我的 HTTP 属性路由,所以我没有将我的 webapi 方法映射到我放在它们上面的 [Authorize] 装饰。 (该行: config.MapHttpAttributeRoutes(); 在该方法中)。

    希望这可以帮助其他人。您可以做很多其他事情来保护和配置您的 OWIN 服务器,但这应该让您开始使用相对安全的 CORS api 解决方案来开始您的开发。

    【讨论】:

      猜你喜欢
      • 2014-10-22
      • 2019-09-13
      • 1970-01-01
      • 2016-02-06
      • 2017-09-05
      • 2015-01-25
      • 1970-01-01
      • 1970-01-01
      • 2015-09-23
      相关资源
      最近更新 更多