[英]C# EntityFramework Core Endopoint JWT Authentication
我正在使用ASP.NET Core和Entity Framework Core編寫的Web API。
目前,我正面臨一個使我無法入睡的問題:)
我使用名為TokenProviderMiddleWare的類保護端點(此教程頁上為: https : //stormpath.com/blog/token-authentication-asp-net-core )
這是我從數據庫檢索用戶並檢查提供的密碼是否與數據庫匹配的功能:
private Task<ClaimsIdentity> GetUserIdentity(string email, string password)
{
var driver = context.Drivers.SingleOrDefault(d => d.Email == email);
if (driver == null)
return Task.FromResult<ClaimsIdentity>(null);
if (driver.Password != password)
{
SetBadLoginAttempt(driver);
context.SaveChangesAsync();
return Task.FromResult<ClaimsIdentity>(null);
}
if (driver.IsLoginDisabled)
{
return Task.FromResult<ClaimsIdentity>(null);
}
ResetBadLoginAttempt(driver);
context.SaveChangesAsync();
return Task.FromResult(new ClaimsIdentity(
new System.Security.Principal.GenericIdentity(email, "Token"),
new Claim[] {
new Claim("fullName", driver.Name),
}
));
}`
如果我同時運行兩次登錄,則會收到此錯誤:
Connection id "0HL5KO6M27JFT": An unhandled exception was thrown by the application.
System.InvalidOperationException: An attempt was made to use the context
while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this
point.
通過在此函數的第一行執行此操作可以解決此問題:
Driver driver = null;
lock(context)
{
driver = context.Drivers.SingleOrDefault(d => d.Email == email);
}
但是我認為這是丑陋的並且不能擴展。
簡而言之,我想通過EntityFramework在數據庫中檢查我的用戶。 我的DbContext是通過.NET Core通過構造函數注入的。 而且我認為存在某種並發問題...
我在其中使用此代碼的此類如下所示:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SmartoonAPI.Persistence;
using System.Linq;
using System.Collections.Generic;
using SmartoonDomain.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace SmartoonAPI.JWT
{
public class TokenProviderMiddleware
{
private readonly RequestDelegate next;
private readonly TokenProviderOptions options;
private readonly ISmartoonContext context;
public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, ISmartoonContext context)
{
this.context = context;
this.next = next;
this.options = options.Value;
}
public Task Invoke(HttpContext context)
{
// If the request path doesn't match, skip
if (!context.Request.Path.Equals(options.Path, StringComparison.Ordinal))
{
return next(context);
}
// Request must be POST with Content-Type: application/x-www-form-urlencoded
if (!context.Request.Method.Equals("POST")
|| !context.Request.HasFormContentType)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync("Bad request.");
}
return GenerateToken(context);
}
private async Task GenerateToken(HttpContext context)
{
ClaimsIdentity identity;
if (!string.IsNullOrEmpty(context.Request.Form["email"]) && !string.IsNullOrEmpty(context.Request.Form["password"]))
identity = await GetUserIdentity(context.Request.Form["email"], context.Request.Form["password"]);
else
identity = await GetApplicationIdentity(context.Request.Form["appid"], context.Request.Form["secret"]);
if (identity == null)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Login failed!");
return;
}
var now = DateTime.UtcNow;
// Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var claims = new List<Claim>()
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
};
claims.AddRange(identity.Claims);
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: options.Issuer,
audience: options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(options.Expiration),
signingCredentials: options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
access_token = encodedJwt,
expires_in = (int)options.Expiration.TotalSeconds
};
// Serialize and return the response
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
}
private LoginAble SetBadLoginAttempt(LoginAble loginAble)
{
if (loginAble.LoginDisabledOn.HasValue && (DateTime.Now - loginAble.LoginDisabledOn.Value).TotalMinutes > 30)
{
ResetBadLoginAttempt(loginAble);
loginAble.BadLoginAttempt++;
return loginAble;
}
loginAble.BadLoginAttempt++;
if (loginAble.BadLoginAttempt < 3)
{
return loginAble;
}
else
{
loginAble.IsLoginDisabled = true;
loginAble.LoginDisabledOn = DateTime.Now;
}
return loginAble;
}
private LoginAble ResetBadLoginAttempt(LoginAble loginAble)
{
loginAble.BadLoginAttempt = 0;
loginAble.IsLoginDisabled = false;
loginAble.LoginDisabledOn = null;
return loginAble;
}
private Task<ClaimsIdentity> GetUserIdentity(string email, string password)
{
var driver = context.Drivers.SingleOrDefault(d => d.Email == email);
if (driver == null)
return Task.FromResult<ClaimsIdentity>(null);
if (driver.Password != password)
{
SetBadLoginAttempt(driver);
context.SaveChangesAsync();
return Task.FromResult<ClaimsIdentity>(null);
}
if (driver.IsLoginDisabled)
{
return Task.FromResult<ClaimsIdentity>(null);
}
ResetBadLoginAttempt(driver);
context.SaveChangesAsync();
return Task.FromResult(new ClaimsIdentity(
new System.Security.Principal.GenericIdentity(email, "Token"),
new Claim[] {
new Claim("fullName", driver.Name),
}
));
}
private Task<ClaimsIdentity> GetApplicationIdentity(string appId, string secret)
{
var appCredential = context.AppCredentials.SingleOrDefault(a => a.AppId == appId);
if (appCredential == null)
return Task.FromResult<ClaimsIdentity>(null);
if (appCredential.Secret != secret)
{
SetBadLoginAttempt(appCredential);
context.SaveChangesAsync();
return Task.FromResult<ClaimsIdentity>(null);
}
ResetBadLoginAttempt(appCredential);
context.SaveChangesAsync();
return Task.FromResult(new ClaimsIdentity(
new System.Security.Principal.GenericIdentity(appCredential.AppId, "Token"),
new Claim[] {
new Claim("description", appCredential.Description),
}
));
}
}
}
每個應用程序都會實例化一次ASP.NET Core中間件,而默認情況下,數據庫上下文將在每個請求時實例化,並在完成時處置。 將數據庫上下文直接注入Invoke
方法應該可以解決該問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.