web-dev-qa-db-fra.com

ASP.Net Core - pas de redirection sur l'erreur d'autorisation d'API

Dans mon projet ASP.NET Core, j'ai quelques contrôleurs d'API avec l'autorisation jwt, comme ceci:

[Route("api/v1/[controller]")]
public class MyController : Controller
{
  [HttpGet("[action]")]
  [Authorize(Policy = MyPolicy)]
  public JsonResult FetchAll()
  {
  }
}

Lorsque l'autorisation d'accès à l'action FetchAll () échoue, je souhaite HttpStatusCode.Forbidden comme réponse. Au lieu de cela, Mvc effectue-t-il une redirection vers Compte/Connexion? ReturnUrl = [...]

J'ai essayé de capturer les événements de redirection et de renvoyer en interdisant/interdisant les événements de cookie en vain:

  app.UseIdentity();

  var tokenValidationParameters = new TokenValidationParameters
  {
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = TokenController.DummyKey,
    ValidateIssuer = false,
    ValidateAudience = false,
    ValidateLifetime = true,
    ClockSkew = TimeSpan.FromMinutes(0)
  };
  app.UseJwtBearerAuthentication(new JwtBearerOptions
  {
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = tokenValidationParameters,
  });

  app.UseCookieAuthentication(new CookieAuthenticationOptions()
  {
    AutomaticAuthenticate = false,
    AutomaticChallenge = false,
    AuthenticationScheme = "BsCookie",
    CookieName = "access_token",
    TicketDataFormat = new CustomJwtDataFormat(SecurityAlgorithms.HmacSha256, tokenValidationParameters),
    Events = new CookieAuthenticationEvents
    {
      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);
      },

      OnRedirectToAccessDenied = context =>
      {
        if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == (int)HttpStatusCode.OK)
          context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
        else
          context.Response.Redirect(context.RedirectUri);
        return Task.FromResult(0);
      }
    },
  });

Les deux événements ne sont jamais appelés et la sortie Visual Studio indique que fetchall échoue et que Account/Login est renvoyé:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/api/v1/Lehrer/GetAll application/json 
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: Successfully validated the token.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: (null).
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: AuthenticationScheme: Bearer was forbidden.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware:Information: AuthenticationScheme: Identity.Application was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Sam.Learning2.Controllers.LehrerController.GetAll (Sam.Learning2) in 49.7114ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 121.6106ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/Account/Login?ReturnUrl=%2Fapi%2Fv1%2FLehrer%2FGetAll  

Je souhaite que mes API renvoient 401/403 au lieu de rediriger vers Login. Comment y parvenir lorsque le code ci-dessus ne fonctionne pas

17
Sam

Mettre à jour ASP.NET Core 2.x

L'autorisation a un peu changé dans ASP.NET Core 2.0. La réponse ci-dessous est uniquement valable pour ASP.NET Core 1.x. Pour ASP.NET Core 2.0, référez-vous à ceci answer et this GitHub annoucement .

ASP.NET Core 1.x

Ce que vous semblez avoir oublié, c'est que app.UseIdentity() également enregistre le middleware de cookie .

var options = app.ApplicationServices.GetRequiredService<IOptions<IdentityOptions>>().Value;
app.UseCookieAuthentication(options.Cookies.ExternalCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorRememberMeCookie);
app.UseCookieAuthentication(options.Cookies.TwoFactorUserIdCookie);
app.UseCookieAuthentication(options.Cookies.ApplicationCookie);

et ASP.NET Core Identity définit la variable AutomaticChallange sur true pour le middleware de cookie (ApplicationCookie) ( voir source ). D'où la redirection vers /Account/Login?ReturnUrl. Vous devrez désactiver cette option dans Identity. 

services.AddIdentity(options =>
{
    options.Cookies.ApplicationCookie.AutomaticChallenge = false;
});

Si vous vraiment voulez avoir l’authentification d’identité (connexion à la page Web) et JWT, vous devez enregistrer les middlewares sur la base de l’URL. Donc, i.e. app.UseIdentity() n’est enregistré que pour les URL non-api et le middleware Jwt n’est enregistré que pour les URL commençant par /api.

Vous pouvez le faire avec .MapWhen ( docs ).

app.MapWhen(context => !context.Request.Path.StartsWith("/api"), branch => 
{
    branch.UseIdentity();
});

Désormais, branch.UseIdentity() ne sera utilisé que pour les URL ne commençant pas par /api, qui sont généralement vos vues MVC où la redirection vers /Account/Login est souhaitée. 

18
Tseng

Je viens d'utiliser Barry Dorrans Asp Net Authorization Workshop

dans ConfigureServices j'ajoute simplement services.AddAuthorization();.

et dans Configure ajouter ce code:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "Cookie",
    LoginPath = new PathString("/Account/Login/"),
    AccessDeniedPath = new PathString("/Account/Forbidden/"),
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new CookieAuthenticationEvents()
    {
        OnRedirectToLogin = (ctx) =>
        {
            if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
            {
                ctx.Response.StatusCode = 401;
            }
            else
                ctx.Response.Redirect(ctx.RedirectUri);

            return Task.CompletedTask;
        },
        OnRedirectToAccessDenied = (ctx) =>
        {
            if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
            {
                ctx.Response.StatusCode = 403;
            }
            else
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
            return Task.CompletedTask;
        }
    }
}

Dans Mvc, redirigez vers Compte/Connexion? ReturnUrl = [...] et dans l'API, vous obtiendrez 401 ou 403.

8
akaco

La pile d’API Web de Microsoft est configurée pour le faire immédiatement. La solution est chez le client.

Ajoutez cet en-tête à la demande du client:

'X-Requested-With': 'XMLHttpRequest'

L'API Web recherche cet en-tête. Lorsqu'il est présent, il renvoie 401 si la demande n'est pas authentifiée. Lorsque l'en-tête est absent, la redirection vers la page de connexion est renvoyée.

Voir ceci https://github.com/aspnet/Security/issues/1394#issuecomment-326445124

Je pense que vous n'avez besoin du code plus complexe dans les événements de cookie si vous ne pouvez pas modifier le client.

0
RichardHowells