【问题标题】:ASP.NET Core 2 API call is redirected (302)ASP.NET Core 2 API 调用被重定向 (302)
【发布时间】:2017-09-04 11:52:52
【问题描述】:

我正在尝试迁移此项目 https://github.com/asadsahi/AspNetCoreSpa 从.net core 1.1 到 2.0 但是登录成功后出现问题。登录后,我的 GET api 调用 e。 G。到https://localhost:44331/api/profile/test 以重定向(302)结束,我不知道为什么。我收到了一个不记名令牌,看起来不错。

请求头格式: 授权:Bearer [token]

[Route("api/[controller]")]
public class ProfileController : BaseController
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly ILogger _logger;

    public ProfileController(ILoggerFactory loggerFactory, UserManager<ApplicationUser> userManager)
    {
        _logger = loggerFactory.CreateLogger<ProfileController>();
        _userManager = userManager;
    }

    [HttpGet("test")]
    public async Task<IActionResult> Test()
    {
        return  Json(ModelState.GetModelErrors());
    }
}

[Authorize]
[ServiceFilter(typeof(ApiExceptionFilter))]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public class BaseController : Controller
{
    public BaseController()
    {
    }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    if (_hostingEnv.IsDevelopment())
    {
        services.AddSslCertificate(_hostingEnv);
    }
    else
    {
        services.Configure<MvcOptions>(o => o.Filters.Add(new RequireHttpsAttribute()));
    }
    services.AddOptions();
    services.AddCors();
    services.AddLogging();
    services.AddResponseCompression(options =>
    {
        options.MimeTypes = Helpers.DefaultMimeTypes;
    });

    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


    }).AddJwtBearer(cfg =>
    {
        cfg.SaveToken = true;
        cfg.TokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = Configuration["Authentication:BearerTokens:Issuer"],
            ValidAudience = Configuration["Authentication:BearerTokens:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authentication:BearerTokens:Key"])),
            ValidateIssuerSigningKey = false,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
        cfg.Events = new JwtBearerEvents
        {

            OnAuthenticationFailed = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
                logger.LogError("Authentication failed.", context.Exception);
                return Task.CompletedTask;
            },

            OnMessageReceived = context =>
            {
                return Task.CompletedTask;
            },
            OnChallenge = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
                logger.LogError("OnChallenge error", context.Error, context.ErrorDescription);
                return Task.CompletedTask;
            }
        };
    });

    services.AddDbContext<ApplicationDbContext>(options =>
    {
        string useSqLite = Startup.Configuration["Data:useSqLite"];
        if (useSqLite.ToLower() == "true")
        {
            options.UseSqlite(Startup.Configuration["Data:SqlLiteConnectionString"]);
        }
        else
        {
            options.UseSqlServer(Startup.Configuration["Data:SqlServerConnectionString"]);
        }
        options.UseOpenIddict();
    });


    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    //services.ConfigureApplicationCookie(options =>
    //{

    //    options.LoginPath = "/login";
    //    options.Events.OnRedirectToLogin = context =>
    //    {
    //        if (context.Request.Path.StartsWithSegments("/api") &&
    //            context.Response.StatusCode == (int)HttpStatusCode.OK)
    //        {
    //            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    //        }
    //        else
    //        {
    //            context.Response.Redirect(context.RedirectUri);
    //        }
    //        return Task.FromResult(0);
    //    };
    //});


    services.AddOAuthProviders();

    services.AddCustomOpenIddict();

    services.AddMemoryCache();

    services.RegisterCustomServices();

    services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

    services.AddCustomizedMvc();

    // Node services are to execute any arbitrary nodejs code from .net
    services.AddNodeServices();

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "AspNetCoreSpa", Version = "v1" });
    });
}

public void Configure(IApplicationBuilder app)
{
    app.AddDevMiddlewares();

    if (_hostingEnv.IsProduction())
    {
        app.UseResponseCompression();
    }

    app.SetupMigrations();

    app.UseXsrf();

    app.UseStaticFiles();

    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        // http://stackoverflow.com/questions/25982095/using-googleoauth2authenticationoptions-got-a-redirect-uri-mismatch-error
        routes.MapRoute(name: "signin-google", template: "signin-google", defaults: new { controller = "Account", action = "ExternalLoginCallback" });

        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });
}

我的 IServiceCollection-Extensions:

public static IServiceCollection AddCustomizedMvc(this IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(ModelValidationFilter));
    })
    .AddJsonOptions(options =>
    {
        options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    });

    return services;
}

public static IServiceCollection AddOAuthProviders(this IServiceCollection services)
{
    services.AddAuthentication()
        .AddFacebook(o =>
        {
            o.AppId = Startup.Configuration["Authentication:Facebook:AppId"];
            o.AppSecret = Startup.Configuration["Authentication:Facebook:AppSecret"];
        });

    services.AddAuthentication()
        .AddGoogle(o =>
        {
            o.ClientId = Startup.Configuration["Authentication:Google:ClientId"];
            o.ClientSecret = Startup.Configuration["Authentication:Google:ClientSecret"];
        });
    services.AddAuthentication()
        .AddTwitter(o =>
        {
            o.ConsumerKey = Startup.Configuration["Authentication:Twitter:ConsumerKey"];
            o.ConsumerSecret = Startup.Configuration["Authentication:Twitter:ConsumerSecret"];
        });

    services.AddAuthentication()
        .AddMicrosoftAccount(o =>
        {
            o.ClientId= Startup.Configuration["Authentication:Microsoft:ClientId"];
            o.ClientSecret = Startup.Configuration["Authentication:Microsoft:ClientSecret"];
        });

    return services;
}

public static IServiceCollection AddCustomOpenIddict(this IServiceCollection services)
{

    // Configure Identity to use the same JWT claims as OpenIddict instead
    // of the legacy WS-Federation claims it uses by default (ClaimTypes),
    // which saves you from doing the mapping in your authorization controller.
    services.Configure<IdentityOptions>(options =>
    {
        options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
        options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
        options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;

    });

    // Register the OpenIddict services.
    services.AddOpenIddict()
        // Register the Entity Framework stores.
        .AddEntityFrameworkCoreStores<ApplicationDbContext>()

        // Register the ASP.NET Core MVC binder used by OpenIddict.
        // Note: if you don't call this method, you won't be able to
        // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
        .AddMvcBinders()

        // Enable the token endpoint.
        .EnableTokenEndpoint("/connect/token")

        // Enable the password and the refresh token flows.
        .AllowPasswordFlow()
        .AllowRefreshTokenFlow()

        // During development, you can disable the HTTPS requirement.
        .DisableHttpsRequirement()

        // Register a new ephemeral key, that is discarded when the application
        // shuts down. Tokens signed using this key are automatically invalidated.
        // This method should only be used during development.
        .AddEphemeralSigningKey();

    // On production, using a X.509 certificate stored in the machine store is recommended.
    // You can generate a self-signed certificate using Pluralsight's self-cert utility:
    // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.zip
    //
    // services.AddOpenIddict()
    //     .AddSigningCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75");
    //
    // Alternatively, you can also store the certificate as an embedded .pfx resource
    // directly in this assembly or in a file published alongside this project:
    //
    // services.AddOpenIddict()
    //     .AddSigningCertificate(
    //          assembly: typeof(Startup).GetTypeInfo().Assembly,
    //          resource: "AuthorizationServer.Certificate.pfx",
    //          password: "OpenIddict");

    return services;
}

public static IServiceCollection AddCustomDbContext(this IServiceCollection services)
{
    // Add framework services.

    return services;
}

public static IServiceCollection RegisterCustomServices(this IServiceCollection services)
{
    // New instance every time, only configuration class needs so its ok
    services.Configure<SmsSettings>(options => Startup.Configuration.GetSection("SmsSettingsTwillio").Bind(options));
    services.AddTransient<UserResolverService>();
    services.AddTransient<IEmailSender, EmailSender>();
    services.AddTransient<ISmsSender, SmsSender>();
    services.AddScoped<ApiExceptionFilter>();
    return services;
}

这是我的包裹:

<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.Introspection" Version="2.0.0-*" />
<PackageReference Include="AspNet.Security.OAuth.Validation" Version="2.0.0-*" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.AzureAppServicesIntegration" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Antiforgery" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Twitter" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.AngularServices" Version="1.1.0-beta-000002" />
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="1.0.0-beta3-final" />
<PackageReference Include="AspNet.Security.OAuth.LinkedIn" Version="1.0.0-beta3-final" />
<PackageReference Include="OpenIddict" Version="2.0.0-*" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="2.0.0-*" />
<PackageReference Include="OpenIddict.Mvc" Version="2.0.0-*" />
<PackageReference Include="SendGrid" Version="9.9.0" />
<PackageReference Include="MailKit" Version="1.18.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="1.0.0" />
<PackageReference Include="Twilio" Version="5.6.3" />
<PackageReference Include="Stripe.net" Version="10.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Webpack" Version="4.0.0" />
<PackageReference Include="Serilog" Version="2.5.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.Seq" Version="3.3.3" />
<PackageReference Include="Bogus" Version="17.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0">
  <PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0">
  <PrivateAssets>All</PrivateAssets>
</PackageReference>

  <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="2.0.0" PrivateAssets="All" />
  </ItemGroup>

  <ItemGroup>
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>

这是我的日志:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
  Request starting HTTP/1.1 GET http://localhost:44331/api/profile/test 
application/json; charset=UTF-8 
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
  Authorization failed for user: (null).
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
  Authorization failed for user: (null).
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
  Authorization failed for the request at filter 
'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
  Authorization failed for the request at filter 
'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
  Executing ChallengeResult with authentication schemes ().
info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
  Executing ChallengeResult with authentication schemes ().
info: 
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[12]
  AuthenticationScheme: Identity.Application was challenged.
info: 
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[12]
  AuthenticationScheme: Identity.Application was challenged.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
  Executed action 
AspNetCoreSpa.Server.Controllers.api.ProfileController.Test (AspNetCoreSpa) 
in 43.3105ms
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
  Executed action 
AspNetCoreSpa.Server.Controllers.api.ProfileController.Test (AspNetCoreSpa) 
in 43.3105ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  Request finished in 67.4133ms 302 
infoinfo: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  Request finished in 67.4133ms 302 
: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
  Request starting HTTP/1.1 GET http://localhost:44331/Account/Login?
ReturnUrl=%2Fapi%2Fprofile%2Ftest application/json; charset=UTF-8 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
  Request starting HTTP/1.1 GET http://localhost:44331/Account/Login?
ReturnUrl=%2Fapi%2Fprofile%2Ftest application/json; charset=UTF-8 
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
  Executing action method 
AspNetCoreSpa.Server.Controllers.HomeController.Index (AspNetCoreSpa) with 
arguments ((null)) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
  Executing action method 
AspNetCoreSpa.Server.Controllers.HomeController.Index (AspNetCoreSpa) with 
arguments ((null)) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor[1]
  Executing ViewResult, running view at path /Views/Home/Index.cshtml.
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor[1]
  Executing ViewResult, running view at path /Views/Home/Index.cshtml.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
  Executed action AspNetCoreSpa.Server.Controllers.HomeController.Index 
(AspNetCoreSpa) in 13.2746ms
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
  Executed action AspNetCoreSpa.Server.Controllers.HomeController.Index 
(AspNetCoreSpa) in 13.2746ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  Request finished in 79.2352ms 200 text/html; charset=utf-8
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  Request finished in 79.2352ms 200 text/html; charset=utf-8

我想知道以下行:

用户授权失败:(null)

已经找到这个Authorization failed for user: (null) 但目前还没有答案,我认为这是 .NET Core 1 的问题。

【问题讨论】:

  • 我也有同样的问题。 302 正试图将我带到 ~/Account/Login。然后我得到一个 404,因为该路由不存在。
  • 升级到 asp.net core 2 后我也面临同样的问题
  • 这个问题你解决了吗?

标签: c# asp.net-core asp.net-core-2.0 asp.net-core-identity


【解决方案1】:

当您调用 AddIdentity 时,它会添加 Cookie 身份验证,该身份验证会覆盖您预期的 JWT 不记名身份验证。解决此问题的一种方法是在设置 JWT 身份验证之前移动 AddIdentity 调用。以下是适用于我的代码:

// setup identity
services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<MyMoneyDbContext>()
    .AddDefaultTokenProviders();

// setup Jwt authentication
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions =>
{
    jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        ...

另一种选择是使用 AddIdentityCore,但我从未尝试过。

【讨论】:

  • 酷!!!我在重构期间更改了顺序,并开始发生 302 重定向。
  • 天哪,你拯救了我的一天!谢谢
  • 这是一个简单的修复,应该是答案。
【解决方案2】:

我遇到了同样的问题,为了解决这个问题,我必须在控制器的 Authorize 属性中包含身份验证方案。

在你的情况下:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ServiceFilter(typeof(ApiExceptionFilter))]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public class BaseController : Controller
{
    public BaseController()
    {
    }
}

【讨论】:

  • 确实有帮助,但我想知道为什么其他人的所有示例都不需要。
【解决方案3】:

对我来说,它与跨域资源共享 (CORS) 有关。我在 Azure 的应用服务中有一个启用了 CORS 的 API。当我将 API 的调用者添加到允许的来源列表时,302 消失了。

即使我已经在我的 aspnet 核心启动代码中添加了这些来源,我也必须这样做。

【讨论】:

  • 这不是它应该如何工作的权利吗?根据 MSFT,如果我们在 azure app 服务的 cors 部分中没有任何内容,它应该从代码中获取值。我现在也对同一点感到震惊。
猜你喜欢
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 2020-12-02
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
  • 2017-02-07
  • 2021-02-23
相关资源
最近更新 更多