Tengo una aplicación .NET Core 2.0 y tengo un problema con la autorización. Quiero usar la autorización personalizada con solicitudes especiales. Encabezado y autenticación predeterminada estándar. Primero, agrego la configuración en Startup.cs
:
public IServiceProvider ConfigureServices(IServiceCollection services) { // ... services.AddAuthorization(options => { options.AddPolicy(DefaultAuthorizedPolicy, policy => { policy.Requirements.Add(new TokenAuthRequirement()); }); }); services.AddSingleton<IAuthorizationHandler, AuthTokenPolicy>(); // ... }
AuthTokenPolicy.cs
:
public class AuthTokenPolicy : AuthorizationHandler<TokenAuthRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenAuthRequirement requirement) { var filterContext = context.Resource as AuthorizationFilterContext; var response = filterContext.HttpContext.Response; try { // some validation code var isValidToken = isValidTokenTask.Result; if (!isValidToken) { response.StatusCode = 401; return Task.CompletedTask; } response.StatusCode = 200; context.Succeed(requirement); } catch (Exception) { return Task.CompletedTask; } return Task.CompletedTask; } }
y en HomeController.cs
:
[Authorize(Policy = Startup.DefaultAuthorizedPolicy)] public async Task<IActionResult> IsVisible()
Si uso el request.header incorrecto en AuthTokenPolicy
, lo veo, pero en los registros veo este error:
System.InvalidOperationException: no se especificó el esquema de autenticación y no se encontró ningún esquema de desafío predeterminado.\r\n en Microsoft.AspNetCore.Authentication.AuthenticationService.d__11.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se produjo la excepción se lanzó ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(tarea)\r\n en Microsoft.AspNetCore.Mvc. ChallengeResult.d__14.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System .Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n en Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__19.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\na t System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n en Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se produjo la excepción se lanzó ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(tarea)\r\n en Microsoft.AspNetCore.Mvc. Internal.ResourceInvoker.d__15.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n en Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerSe rvices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n en Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.d__3.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r \n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Tarea)\r\n en React.AspNet.BabelFileMiddleware.d__5.MoveNext()\r \n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification( Task task)\r\n en Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext()\r\n--- Fin del seguimiento de la pila desde la ubicación anterior donde se lanzó la excepción ---\r\n en System.Runtime .ExceptionServices.ExceptionDispatchInfo.Throw()\r\n en System.Runtime.CompilerServices.TaskAwaiter.Han dleNonSuccessAndDebuggerNotification(tarea)\r\n en core.common.Middleware.LoggingMiddleware.d__3.MoveNext() en D:\Dev\microservicePDP\Template\core.common\Middleware\LoggingMiddleware.cs:line 72
Después de leer Migración de autenticación e identidad a ASP.NET Core 2.0 , agregué este código en startup.cs
Cita del artículo:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; });
Defina un esquema predeterminado en 2.0 si se cumple una de las siguientes condiciones: Quiere que el usuario inicie sesión automáticamente Usa el atributo [Autorizar] o las políticas de autorización sin especificar esquemas
Agregué AuthenticationScheme
y DefaultChallengeScheme
en ConfigureServices()
. No ayudó, el mismo error aquí. He intentado usar app.UseAuthentication();
en el método Startup.Configure()
, sin resultados.
¿Cómo puedo usar una autorización personalizada sin autenticación?
Muchas de las respuestas anteriores son correctas pero al mismo tiempo complicadas con otros aspectos de authN/authZ. Lo que realmente resuelve la excepción en cuestión es esta línea:
services.AddScheme<YourAuthenticationOptions, YourAuthenticationHandler>(YourAuthenticationSchemeName, options => { options.YourProperty = yourValue; })
Su declaración inicial en la solución marcada no es del todo cierta. Si bien su nueva solución puede lograr su objetivo original, todavía es posible eludir el error original mientras conserva su lógica AuthorizationHandler, siempre que tenga controladores de esquema de autenticación básicos en su lugar, incluso si son esqueletos funcionales.
Hablando en términos generales, los controladores de autenticación y los esquemas están destinados a establecer y validar la identidad, lo que los hace necesarios para que funcionen las políticas/controladores de autorización, ya que se ejecutan bajo la suposición de que ya se ha establecido una identidad.
ASP.NET Dev Haok lo resume mejor aquí: "La autenticación actual no tiene en cuenta la autorización en absoluto, solo se preocupa por producir un ClaimsPrincipal por esquema. La autorización tiene que estar al tanto de la autenticación de alguna manera, por lo que AuthenticationSchemes en la política es un mecanismo para que pueda asociar la política con los esquemas utilizados para crear la entidad de seguridad de notificaciones efectiva para la autorización (o simplemente usa el httpContext.User predeterminado para la solicitud, que se basa en DefaultAuthenticateScheme)". https://github.com/aspnet/Security/issues/1469
En mi caso, la solución en la que estoy trabajando proporcionó su propio concepto implícito de identidad, por lo que no necesitábamos esquemas/controladores de autenticación, solo tokens de encabezado para la autorización. Entonces, hasta que nuestros conceptos de identidad cambien, nuestros controladores de autorización de token de encabezado que hacen cumplir las políticas pueden vincularse a esqueletos de esquema 1 a 1.
Etiquetas en puntos finales:
[Authorize(AuthenticationSchemes = "AuthenticatedUserSchemeName", Policy = "AuthorizedUserPolicyName")]
Inicio.cs:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "AuthenticatedUserSchemeName"; }).AddScheme<ValidTokenAuthenticationSchemeOptions, ValidTokenAuthenticationHandler>("AuthenticatedUserSchemeName", _ => { }); services.AddAuthorization(options => { options.AddPolicy("AuthorizedUserPolicyName", policy => { //policy.RequireClaim(ClaimTypes.Sid,"authToken"); policy.AddAuthenticationSchemes("AuthenticatedUserSchemeName"); policy.AddRequirements(new ValidTokenAuthorizationRequirement()); }); services.AddSingleton<IAuthorizationHandler, ValidTokenAuthorizationHandler>();
Se llama tanto al controlador de autenticación vacío como al controlador de autorización (similar en la configuración a las publicaciones respectivas de OP), pero el controlador de autorización aún aplica nuestras políticas de autorización.
No utilice la autorización en lugar de la autenticación. Debería obtener acceso completo para atender a todos los clientes con encabezado.
El código de trabajo es:
public class TokenAuthenticationHandler : AuthenticationHandler<TokenAuthenticationOptions> { public IServiceProvider ServiceProvider { get; set; } public TokenAuthenticationHandler (IOptionsMonitor<TokenAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider) : base (options, logger, encoder, clock) { ServiceProvider = serviceProvider; } protected override Task<AuthenticateResult> HandleAuthenticateAsync () { var headers = Request.Headers; var token = "X-Auth-Token".GetHeaderOrCookieValue (Request); if (string.IsNullOrEmpty (token)) { return Task.FromResult (AuthenticateResult.Fail ("Token is null")); } bool isValidToken = false; // check token here if (!isValidToken) { return Task.FromResult (AuthenticateResult.Fail ($"Balancer not authorize token : for token={token}")); } var claims = new [] { new Claim ("token", token) }; var identity = new ClaimsIdentity (claims, nameof (TokenAuthenticationHandler)); var ticket = new AuthenticationTicket (new ClaimsPrincipal (identity), this.Scheme.Name); return Task.FromResult (AuthenticateResult.Success (ticket)); } }
Inicio.cs:
#region Authentication services.AddAuthentication (o => { o.DefaultScheme = SchemesNamesConst.TokenAuthenticationDefaultScheme; }) .AddScheme<TokenAuthenticationOptions, TokenAuthenticationHandler> (SchemesNamesConst.TokenAuthenticationDefaultScheme, o => { }); #endregion
Y micontrolador.cs:
[Authorize(AuthenticationSchemes = SchemesNamesConst.TokenAuthenticationDefaultScheme)] public class MainController : BaseController { ... }
No puedo encontrar TokenAuthenticationOptions ahora, pero estaba vacío. Encontré la misma clase PhoneNumberAuthenticationOptions:
public class PhoneNumberAuthenticationOptions : AuthenticationSchemeOptions { public Regex PhoneMask { get; set; }// = new Regex("7\\d{10}"); }
Debe definir la clase estática SchemesNamesConst
. Algo como:
public static class SchemesNamesConst { public const string TokenAuthenticationDefaultScheme = "TokenAuthenticationScheme"; }
esto funcionó para mí
// using Microsoft.AspNetCore.Authentication.Cookies; // using Microsoft.AspNetCore.Http; services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = new PathString("/auth/login"); options.AccessDeniedPath = new PathString("/auth/denied"); });
Cuando usé la política antes, también configuré el esquema de autenticación predeterminado. Había modificado DefaultPolicy
para que fuera ligeramente diferente. Sin embargo, lo mismo debería funcionar también para la política de agregar.
services.AddAuthorization(options => { options.AddPolicy(DefaultAuthorizedPolicy, policy => { policy.Requirements.Add(new TokenAuthRequirement()); policy.AuthenticationSchemes = new List<string>() { CookieAuthenticationDefaults.AuthenticationScheme } }); });
Tenga en cuenta que la propiedad Default AuthenticationSchemes
utiliza una lista de solo lectura. Creo que sería mejor implementar eso en lugar de List también.