簡體   English   中英

C#EntityFramework核心端點JWT身份驗證

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM