Простое основное приложение ASP.Net для мультитенантности (SaaS) - PullRequest
0 голосов
/ 16 октября 2019

Задача сделать из обычного REST-приложения на ядре asp.net 2.2 - Мультитенант, я сталкивался с такой проблемой: у приложения была авторизация по ролям на основе токенов JWT, но теперь, перед авторизацией, нужно найтикакой арендатор подключен, потому что у каждого арендатора должна быть своя собственная база данных со всеми таблицами, включая пользователей. Арендаторы хранятся в отдельной базе данных. Как можно проверить с каждым запросом, какой арендатор подключается перед авторизацией, чтобы изменить строку подключения к базе данных в контексте и использовать ее для всех запросов этого пользователя. На основе: https://www.codingame.com/playgrounds/5518/multi-tenant-asp-net-core-5---implementing-database-per-tenant-strategy

Мой код контекста

 using Forest.TenantProvider;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Forest.Models
{
    public class Context:DbContext
    {

    public DbSet<DutyForCuteMaterial>DutyForCuteMaterials { get; set; }
    public DbSet<DutyGluedTimberProduction> dutyGluedTimberProductions { get; set; }
    public DbSet<DutyOnDry> dutyOnDries { get; set; }
    public DbSet<DutyOnHiating> dutyOnHiatings { get; set; }
    public DbSet<DutyOnStarboardKnots> dutyOnStarboardKnots { get; set; }
    public DbSet<Invois> Invoises { get; set; }
    public DbSet<InvoisForWasteMarket> InvoisForWastes { get; set; }
    public DbSet<StockOfCutMaterial> StockOfCutMaterials { get; set; }
    public DbSet<StockOfDryLumber> StockOfDryLumbers { get; set; }
    public DbSet<StockOfFuelCapsule> StockOfFuelCapsules { get; set; }
    public DbSet<StockOfGluedTimber> StockOfGluedTimbers { get; set; }
    public DbSet<StockOfSawdust> StockOfSawdusts { get; set; }
    public DbSet<StockOfTrimming> StockOfTrimmings { get; set; }
    public DbSet<StockWaste> StockWastes  { get; set; }
    public DbSet<TimberIncome> TimberIncomes { get; set; }
    public DbSet<Workers> Workers { get; set; }
    public DbSet<Materials> Materials { get; set; }
    public DbSet<DutyOnFuelCapsule> DutyOnFuelCapsules { get; set; }
    public DbSet<GeneralStock> GeneralStocks { get; set; }
    public DbSet<Person> Users { get; set; }

    private Tenant _tenant;
    private ILogger<Context> _logger;

    public Context(ITenantProvider tenantProvider, ILogger<Context> logger) 
    {
        _tenant = tenantProvider.GetTenant(1);//now just takes 1st tennant in db
        _logger = logger;
        Database.EnsureCreated();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
       optionsBuilder.UseSqlServer(_tenant.DatabaseConnectionString);
       base.OnConfiguring(optionsBuilder);
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    }}

Мой запуск

   using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Serilog;

namespace Forest
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

    public IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
               .AddJwtBearer(options =>
               {
                   options.RequireHttpsMetadata = false;
                   options.TokenValidationParameters = new TokenValidationParameters
                   {

                        ValidateIssuer = true,

                        ValidIssuer = AuthOptions.ISSUER,
                        ValidateAudience = true,
                        ValidAudience = AuthOptions.AUDIENCE,
                        ValidateLifetime = true,
                        IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
                        ValidateIssuerSigningKey = true,
                   };
               });
        services.AddTransient<UserResolveService>();
        string con = Configuration.GetConnectionString("DefaultConnection");
        services.AddDbContext<TenantContext>(options => options.UseSqlServer(con));
        services.AddDbContext<Context>(o => { });
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        services.AddMvc();
        services.AddTransient<ITenantProvider, DummyTenantProvider>();
    }


    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddSerilog();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {

            app.UseHsts();
        }

        app.UseMiddleware<SerilogMiddleware>();
        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseHttpsRedirection();
        app.UseAuthentication();
        app.UseMvc();

    }
}
}

И мой AuthController

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Newtonsoft.Json;
using System.Security.Claims;
using Forest.JWTAuth;
using Forest.Models;

namespace Forest.Controllers
{
    [Route("api/[controller]")]
    public class AccountController:Controller
    {
        Context db;
        TenantContext  conT;
        string a;
        public AccountController(TenantContext contextT ,Context context)
        {
            this.conT = contextT;
            this.db = context;
        }
        [HttpPost]
        public async Task Token([FromBody]AuthData data)
        {
            var username = data.username;
            var password = data.userpassword;
            var TenantName = data.tenantName;
            var identity = GetIdentity(username, password,TenantName);
            if (identity == null)
            {
                Response.StatusCode = 400;
                await Response.WriteAsync("Invalid username or password.");

            }

        var now = DateTime.UtcNow;

        var jwt = new JwtSecurityToken(
                issuer: AuthOptions.ISSUER,
                audience: AuthOptions.AUDIENCE,
                notBefore: now,
                claims: identity.Claims,
                expires: now.Add(TimeSpan.FromMinutes(AuthOptions.LIFETIME)),
                signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256));
        var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

        var response = new
        {
            access_token = encodedJwt,
            username = identity.Name
        };


        Response.ContentType = "application/json";
        await Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));

    }
    private ClaimsIdentity GetIdentity(string username, string password,string tenantName)
    {

        Person person = db.Users.FirstOrDefault(x => x.Login == username && x.Password == password);
        if (person != null)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimsIdentity.DefaultNameClaimType, person.Login),
                new Claim(ClaimsIdentity.DefaultRoleClaimType, person.Role),

            };
            ClaimsIdentity claimsIdentity =
            new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType,
                ClaimsIdentity.DefaultRoleClaimType);
            return claimsIdentity;
        }

        return null;
    }
}
...