Angular - WebAPI2 OWIN身份验证返回401

时间:2016-06-02 13:12:07

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

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

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

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

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

我已经在墙上撞了几天,认为我只需要把它放在那里,希望有人可以向我指出一些明显的东西。

2 个答案:

答案 0 :(得分:1)

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

在login.aspx.cs中使用此机制
                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/");

答案 1 :(得分:1)

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

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 origin(app.UseCors(CorsOptions.AllowAll))。不要这样做。它对开发很好,但你很可能想要锁定你的请求来源。您可以在OAuthServerOptions中设置Provider时执行此操作。

  /// <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学到了很多。他在教程中使用了allow all for CORS,在任何情况下都不应该这样做,除非你真的希望允许你的API从所有来源访问。

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

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