Estoy luchando con la forma de configurar la autenticación en mi servicio web. El servicio se compila con la API web ASP.NET Core.
Todos mis clientes (aplicaciones WPF) deben usar las mismas credenciales para llamar a las operaciones del servicio web.
Después de investigar un poco, se me ocurrió la autenticación básica: enviar un nombre de usuario y una contraseña en el encabezado de la solicitud HTTP. Pero después de horas de investigación, me parece que la autenticación básica no es el camino a seguir en ASP.NET Core.
La mayoría de los recursos que encontré están implementando la autenticación mediante OAuth o algún otro middleware. Pero eso parece ser demasiado grande para mi escenario, además de usar la parte de Identidad de ASP.NET Core.
Entonces, ¿cuál es la forma correcta de lograr mi objetivo: autenticación simple con nombre de usuario y contraseña en un servicio web ASP.NET Core?
¡Gracias por adelantado!
Ahora, después de que me señalaron en la dirección correcta, aquí está mi solución completa:
Esta es la clase de middleware que se ejecuta en cada solicitud entrante y verifica si la solicitud tiene las credenciales correctas. Si no hay credenciales presentes o si son incorrectas, el servicio responde con un error 401 no autorizado de inmediato.
public class AuthenticationMiddleware { private readonly RequestDelegate _next; public AuthenticationMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { string authHeader = context.Request.Headers["Authorization"]; if (authHeader != null && authHeader.StartsWith("Basic")) { //Extract credentials string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim(); Encoding encoding = Encoding.GetEncoding("iso-8859-1"); string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword)); int seperatorIndex = usernamePassword.IndexOf(':'); var username = usernamePassword.Substring(0, seperatorIndex); var password = usernamePassword.Substring(seperatorIndex + 1); if(username == "test" && password == "test" ) { await _next.Invoke(context); } else { context.Response.StatusCode = 401; //Unauthorized return; } } else { // no authorization header context.Response.StatusCode = 401; //Unauthorized return; } } }
La extensión de middleware debe llamarse en el método Configure de la clase de inicio del servicio
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseMiddleware<AuthenticationMiddleware>(); app.UseMvc(); }
¡Y eso es todo! :)
Puede encontrar un muy buen recurso para middleware en .Net Core y autenticación aquí: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/
fuente
Para usar esto solo para controladores específicos, por ejemplo, use esto:
app.UseWhen(x => (x.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)), builder => { builder.UseMiddleware<AuthenticationMiddleware>(); });
fuente
Creo que puedes ir con JWT (Json Web Tokens).
Primero necesitas instalar el paquete System.IdentityModel.Tokens.Jwt:
$ dotnet add package System.IdentityModel.Tokens.Jwt
Deberá agregar un controlador para la generación y autenticación de tokens como este:
public class TokenController : Controller { [Route("/token")] [HttpPost] public IActionResult Create(string username, string password) { if (IsValidUserAndPasswordCombination(username, password)) return new ObjectResult(GenerateToken(username)); return BadRequest(); } private bool IsValidUserAndPasswordCombination(string username, string password) { return !string.IsNullOrEmpty(username) && username == password; } private string GenerateToken(string username) { var claims = new Claim[] { new Claim(ClaimTypes.Name, username), new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString()), new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds().ToString()), }; var token = new JwtSecurityToken( new JwtHeader(new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")), SecurityAlgorithms.HmacSha256)), new JwtPayload(claims)); return new JwtSecurityTokenHandler().WriteToken(token); } }
Después de esa actualización, la clase Startup.cs se verá a continuación:
namespace WebAPISecurity { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; }) .AddJwtBearer("JwtBearer", jwtBearerOptions => { jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Secret Key You Devise")), ValidateIssuer = false, //ValidIssuer = "The name of the issuer", ValidateAudience = false, //ValidAudience = "The name of the audience", ValidateLifetime = true, //validate the expiration and not before values in the token ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } }
Y eso es todo, lo que queda ahora es poner
[Authorize]
atributo en los Controladores o Acciones que desee.Aquí hay un enlace de un tutorial completo y sencillo.
http://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/
fuente
Lo he implementado
BasicAuthenticationHandler
para la autenticación básica para que pueda usarlo con atributos estándarAuthorize
yAllowAnonymous
.public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions> { protected override Task<AuthenticateResult> HandleAuthenticateAsync() { var authHeader = (string)this.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase)) { //Extract credentials string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim(); Encoding encoding = Encoding.GetEncoding("iso-8859-1"); string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword)); int seperatorIndex = usernamePassword.IndexOf(':', StringComparison.OrdinalIgnoreCase); var username = usernamePassword.Substring(0, seperatorIndex); var password = usernamePassword.Substring(seperatorIndex + 1); //you also can use this.Context.Authentication here if (username == "test" && password == "test") { var user = new GenericPrincipal(new GenericIdentity("User"), null); var ticket = new AuthenticationTicket(user, new AuthenticationProperties(), Options.AuthenticationScheme); return Task.FromResult(AuthenticateResult.Success(ticket)); } else { return Task.FromResult(AuthenticateResult.Fail("No valid user.")); } } this.Response.Headers["WWW-Authenticate"]= "Basic realm=\"yourawesomesite.net\""; return Task.FromResult(AuthenticateResult.Fail("No credentials.")); } } public class BasicAuthenticationMiddleware : AuthenticationMiddleware<BasicAuthenticationOptions> { public BasicAuthenticationMiddleware( RequestDelegate next, IOptions<BasicAuthenticationOptions> options, ILoggerFactory loggerFactory, UrlEncoder encoder) : base(next, options, loggerFactory, encoder) { } protected override AuthenticationHandler<BasicAuthenticationOptions> CreateHandler() { return new BasicAuthenticationHandler(); } } public class BasicAuthenticationOptions : AuthenticationOptions { public BasicAuthenticationOptions() { AuthenticationScheme = "Basic"; AutomaticAuthenticate = true; } }
Registro en Startup.cs -
app.UseMiddleware<BasicAuthenticationMiddleware>();
. Con este código, puede restringir cualquier controlador con el atributo estándar Autorize:[Authorize(ActiveAuthenticationSchemes = "Basic")] [Route("api/[controller]")] public class ValuesController : Controller
y use el atributo
AllowAnonymous
si aplica el filtro de autorización en el nivel de la aplicación.fuente
En este repositorio público de Github https://github.com/boskjoett/BasicAuthWebApi , puede ver un ejemplo simple de una API web ASP.NET Core 2.2 con puntos finales protegidos por autenticación básica.
fuente
Como se dijo correctamente en publicaciones anteriores, una de las formas es implementar un middleware de autenticación básico personalizado. Encontré el mejor código de trabajo con explicación en este blog: autenticación básica con middleware personalizado
Remití el mismo blog pero tuve que hacer 2 adaptaciones:
Mientras lee el nombre de usuario y la contraseña del archivo appsettings.json, agregue la propiedad estática de solo lectura en el archivo de inicio. Luego lea de appsettings.json. Finalmente, lea los valores desde cualquier lugar del proyecto. Ejemplo:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public static string UserNameFromAppSettings { get; private set; } public static string PasswordFromAppSettings { get; private set; } //set username and password from appsettings.json UserNameFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("UserName").Value; PasswordFromAppSettings = Configuration.GetSection("BasicAuth").GetSection("Password").Value; }
fuente
Puedes usar un
ActionFilterAttribute
public class BasicAuthAttribute : ActionFilterAttribute { public string BasicRealm { get; set; } protected NetworkCredential Nc { get; set; } public BasicAuthAttribute(string user,string pass) { this.Nc = new NetworkCredential(user,pass); } public override void OnActionExecuting(ActionExecutingContext filterContext) { var req = filterContext.HttpContext.Request; var auth = req.Headers["Authorization"].ToString(); if (!String.IsNullOrEmpty(auth)) { var cred = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(auth.Substring(6))) .Split(':'); var user = new {Name = cred[0], Pass = cred[1]}; if (user.Name == Nc.UserName && user.Pass == Nc.Password) return; } filterContext.HttpContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel")); filterContext.Result = new UnauthorizedResult(); } }
y agrega el atributo a tu controlador
[BasicAuth("USR", "MyPassword")]
fuente
ASP.NET Core 2.0 con Angular
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
Asegúrese de utilizar el tipo de filtro de autenticación
[Autorizar (AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
fuente