Mam problem z konfiguracją uwierzytelniania w mojej usłudze internetowej. Usługa jest tworzona przy użyciu internetowego interfejsu API ASP.NET Core.
Wszyscy moi klienci (aplikacje WPF) powinni używać tych samych poświadczeń do wywoływania operacji usługi sieci Web.
Po kilku poszukiwaniach wymyśliłem uwierzytelnianie podstawowe - wysyłanie nazwy użytkownika i hasła w nagłówku żądania HTTP. Ale po wielu godzinach badań wydaje mi się, że podstawowe uwierzytelnianie nie jest właściwą drogą w ASP.NET Core.
Większość znalezionych zasobów implementuje uwierzytelnianie przy użyciu protokołu OAuth lub innego oprogramowania pośredniego. Ale wydaje się, że jest to zbyt duże w moim scenariuszu, a także przy użyciu części Identity ASP.NET Core.
Jaki jest więc właściwy sposób osiągnięcia celu - prostego uwierzytelniania przy użyciu nazwy użytkownika i hasła w usłudze sieci Web ASP.NET Core?
Z góry dziękuję!
Teraz, gdy zostałem wskazany we właściwym kierunku, oto moje kompletne rozwiązanie:
Jest to klasa oprogramowania pośredniego, która jest wykonywana przy każdym przychodzącym żądaniu i sprawdza, czy żądanie ma prawidłowe poświadczenia. Jeśli nie ma poświadczeń lub są one błędne, usługa natychmiast odpowiada błędem 401 Unauthorized .
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; } } }
Rozszerzenie oprogramowania pośredniego należy wywołać w metodzie Configure klasy startowej usługi
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseMiddleware<AuthenticationMiddleware>(); app.UseMvc(); }
I to wszystko! :)
Bardzo dobre źródło oprogramowania pośredniego w .Net Core i uwierzytelniania można znaleźć tutaj: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/
źródło
Aby użyć tego tylko dla określonych kontrolerów, na przykład użyj tego:
app.UseWhen(x => (x.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)), builder => { builder.UseMiddleware<AuthenticationMiddleware>(); });
źródło
Myślę, że możesz skorzystać z JWT (Json Web Tokens).
Najpierw musisz zainstalować pakiet System.IdentityModel.Tokens.Jwt:
$ dotnet add package System.IdentityModel.Tokens.Jwt
Będziesz musiał dodać kontroler do generowania tokenów i uwierzytelniania, taki jak ten:
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); } }
Następnie zaktualizuj klasę Startup.cs, aby wyglądała jak poniżej:
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(); } }
I to wszystko, co teraz pozostaje, to umieścić
[Authorize]
atrybut na kontrolerach lub akcjach, które chcesz.Oto link do pełnego, prostego samouczka.
http://www.blinkingcaret.com/2017/09/06/secure-web-api-in-asp-net-core/
źródło
Zaimplementowałem
BasicAuthenticationHandler
dla podstawowego uwierzytelniania, więc możesz go używać ze standardowymi atrybutamiAuthorize
iAllowAnonymous
.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; } }
Rejestracja na Startup.cs -
app.UseMiddleware<BasicAuthenticationMiddleware>();
. Za pomocą tego kodu możesz ograniczyć dowolny kontroler za pomocą atrybutu standardowego Autorize:[Authorize(ActiveAuthenticationSchemes = "Basic")] [Route("api/[controller]")] public class ValuesController : Controller
i użyj atrybutu,
AllowAnonymous
jeśli zastosujesz filtr autoryzacji na poziomie aplikacji.źródło
W tym publicznym repozytorium Github https://github.com/boskjoett/BasicAuthWebApi można zobaczyć prosty przykład internetowego interfejsu API ASP.NET Core 2,2 z punktami końcowymi chronionymi przez uwierzytelnianie podstawowe.
źródło
Jak słusznie powiedziano w poprzednich postach, jednym ze sposobów jest zaimplementowanie niestandardowego oprogramowania pośredniczącego do podstawowego uwierzytelniania. Znalazłem najlepszy działający kod z wyjaśnieniem na tym blogu: Basic Auth with custom middleware
Odniosłem się do tego samego bloga, ale musiałem zrobić 2 adaptacje:
Podczas odczytywania nazwy użytkownika i hasła z pliku appsettings.json dodaj statyczną właściwość tylko do odczytu w pliku startowym. Następnie przeczytaj plik appsettings.json. Na koniec przeczytaj wartości z dowolnego miejsca w projekcie. Przykład:
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; }
źródło
Możesz użyć
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(); } }
i dodaj atrybut do kontrolera
[BasicAuth("USR", "MyPassword")]
źródło
ASP.NET Core 2.0 z Angular
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
Upewnij się, że używasz typu filtru uwierzytelniania
[Autoryzuj (AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
źródło