我想在同一个数据库上使用 web (MVC) 和 API

I want web (MVC) and API on same database

我有一个数据库,需要用于管理(交易数量、计费和其他管理)的 Web 界面,并提供来自数据库(产品)的数据 "manually" 和 API 来提供服务数据(产品)给其他大客户。显然,所有这些都由 SSL 和 https 保护。

我制作了一个 asp.net MVC 5 应用程序(业务逻辑和管理)并想实现 API(API 中的菜鸟)来向用户传送数据。

不知道如何实现从 MVC 到 API(同一数据库)的安全性。

应用很小,我可以重写它。我正在考虑尝试使用核心,但担心我会遇到同样的问题。

具体问题:我应该采用什么方法以及它应该在 MVC 5 代或 .core (MVC 6) 中才能使用一个数据库来存储数据、用户以及他们的授权?

(将一切都推向真实 API 是我想避免的事情)

好的,我的项目完成了。我在 MVC 5 上取得了进展。

(我向你们这些完美主义者道歉,但我现在没有时间删除不必要的东西所以我把整个文件原样转储了:)

第一种方法 - 放弃

首先,我尝试按照 Internet 推荐的方式设计它:.MVC 解决方案、数据库的 .DB 和 .API 解决方案。

结论 - 身份验证和 entity framework 存在很多问题。最后我放弃了这种方法

第二次成功的方法

只有一个解决方案.MVC

真正的 NuGet 安装了 .net Api,使用了几个教程(没有一个有效)扩展了集成授权。请注意,我使用 ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings and Implementing HTTPS Everywhere in ASP.Net MVC application.

修改和插件如下:

App_Start -> IdentityConfig.cs

public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
    // *** ADD INT TYPE ARGUMENT TO CONSTRUCTOR CALL:
    public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
        : base(store)
    {
    }

    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options,
        IOwinContext context)
    {
        // *** PASS CUSTOM APPLICATION USER STORE AS CONSTRUCTOR ARGUMENT:
        var manager = new ApplicationUserManager(
            new ApplicationUserStore(context.Get<ApplicationDbContext>()));

        // Configure validation logic for usernames

        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = false,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };


            // other code removed for brevity      
        manager.UserLockoutEnabledByDefault = Convert.ToBoolean(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"].ToString());
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(Double.Parse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
        manager.MaxFailedAccessAttemptsBeforeLockout = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString());


        // Register two factor authentication providers. 
        // This application uses Phone and Emails as a step of receiving a 
        // code for verifying the user You can write your own provider and plug in here.

        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        //manager.RegisterTwoFactorProvider("PhoneCode",
        //  new PhoneNumberTokenProvider<ApplicationUser, int>
        //  {
        //      MessageFormat = "Your security code is: {0}"
        //  });

        //// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        //manager.RegisterTwoFactorProvider("EmailCode",
        //  new EmailTokenProvider<ApplicationUser, int>
        //  {
        //      Subject = "SecurityCode",
        //      BodyFormat = "Your security code is {0}"
        //  });

        //manager.EmailService = new EmailService();
        //manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser, int>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}


// PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO BASE:
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
    // PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO CONSTRUCTOR:
    public ApplicationRoleManager(IRoleStore<ApplicationRole, int> roleStore)
        : base(roleStore)
    {
    }

    // PASS CUSTOM APPLICATION ROLE AS TYPE ARGUMENT:
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
    }
}


public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        return Task.FromResult(0);
    }
}


public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        return Task.FromResult(0);
    }
}

//This is useful if you do not want to tear down the database each time you run the application.
//public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
//This example shows you how to create a new database if the Model changes
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
    protected override void Seed(ApplicationDbContext context)
    {
        //InitializeIdentityForEF(context); //- Do not Seed - IGOR
        //base.Seed(context);
    }

    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    //public static void InitializeIdentityForEF(ApplicationDbContext db)
    //{
    //  var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    //  var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    //  const string name = "igor@email.mail";
    //  const string password = "LolLol1";
    //  const string roleName = "lol";

    //  //Create Role Admin if it does not exist
    //  var role = roleManager.FindByName(roleName);
    //  if (role == null)
    //  {
    //      role = new ApplicationRole(roleName);
    //      var roleresult = roleManager.Create(role);
    //  }

    //  var user = userManager.FindByName(name);
    //  if (user == null)
    //  {
    //      user = new ApplicationUser { UserName = name, Email = name };
    //      var result = userManager.Create(user, password);
    //      result = userManager.SetLockoutEnabled(user.Id, false);
    //  }

    //  // Add user admin to Role Admin if not already added
    //  var rolesForUser = userManager.GetRoles(user.Id);
    //  if (!rolesForUser.Contains(role.Name))
    //  {
    //      var result = userManager.AddToRole(user.Id, role.Name);
    //  }
    //}
}


public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
    public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
        base(userManager, authenticationManager)
    { }

    public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
    {
        return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
    }

    public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}

App_Start -> Startup.Auth.cs

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }


    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and role manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                    validateInterval: TimeSpan.FromMinutes(2880),
                    regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
                        // Need to add THIS line because we added the third type argument (int) above:
                        getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
            }
        });
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

        // Uncomment the following lines to enable logging in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");

        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");

        //app.UseGoogleAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), //TODO - makni ovo
            AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
            // In production mode set AllowInsecureHttp = false
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
    }
}
//public partial class Startup
//{
//  public void ConfigureAuth(IAppBuilder app)
//  {
//      // Enable the application to use a cookie to store information for the signed in user
//      app.UseCookieAuthentication(new CookieAuthenticationOptions
//      {
//          ExpireTimeSpan = TimeSpan.FromHours(24),
//          CookieSecure = CookieSecureOption.Never,
//          CookieHttpOnly = false,
//          SlidingExpiration = true,
//          AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
//          LoginPath = new PathString("/Account/Login")
//      });
//      // Use a cookie to temporarily store information about a user logging in with a third party login provider
//      app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//  }
//}

App_Start -> WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // TODO: Add any additional configuration code.

        // Web API routes
        config.MapHttpAttributeRoutes();

        //config.Routes.MapHttpRoute(
        //  name: "getkey",
        //  routeTemplate: "api/ApiKeys/Get/{term}"

        //);

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        // WebAPI when dealing with JSON & JavaScript!
        // Setup json serialization to serialize classes to camel (std. Json format)
        var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        formatter.SerializerSettings.ContractResolver =
            new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();

        // make all web-api requests to be sent over https
        config.MessageHandlers.Add(new EnforceHttpsHandler());
    }
}

我用于初始插入和编辑角色和初始用户的 MySysAdmin 控制器。

public SysAdminController(ApplicationUserManager userManager,
        ApplicationRoleManager roleManager)
    {
        UserManager = userManager;
        RoleManager = roleManager;
    }

    private ApplicationUserManager _userManager;
    public ApplicationUserManager UserManager
    {
        get
        {
            return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
        set
        {
            _userManager = value;
        }
    }

    private ApplicationRoleManager _roleManager;
    public ApplicationRoleManager RoleManager
    {
        get
        {
            return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
        }
        private set
        {
            _roleManager = value;
        }
    }

    public ActionResult RoleIndex()
    {
        return View(RoleManager.Roles);
    }

    public ActionResult RoleCreate()
    {
        return View();
    }

    [HttpPost]
    public async Task<ActionResult> RoleCreate(SysAdminVM.RoleViewModel roleViewModel)
    {
        if (ModelState.IsValid)
        {
            // Use ApplicationRole, not IdentityRole:
            var role = new ApplicationRole(roleViewModel.Name);
            var roleresult = await RoleManager.CreateAsync(role);
            if (!roleresult.Succeeded)
            {
                ModelState.AddModelError("", roleresult.Errors.First());
                return View();
            }
            return RedirectToAction("RoleIndex");
        }
        return View();
    }

    public async Task<ActionResult> RoleEdit(int id)
    {
        if (id > 0)
        {
            var role = await RoleManager.FindByIdAsync(id);
            if (role == null)
            {
                return HttpNotFound();
            }
            SysAdminVM.RoleViewModel roleModel = new SysAdminVM.RoleViewModel { Id = role.Id, Name = role.Name };
            return View(roleModel);
        }
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> RoleEdit([Bind(Include = "Name,Id")] SysAdminVM.RoleViewModel roleModel)
    {
        if (ModelState.IsValid)
        {
            var role = await RoleManager.FindByIdAsync(roleModel.Id);
            role.Name = roleModel.Name;
            await RoleManager.UpdateAsync(role);
            return RedirectToAction("RoleIndex");
        }
        return View();
    }

    [AllowAnonymous]
    public async Task<ActionResult> Initialize()
    {
        if (db.App.Where(x => x.Name.Contains("Initialize")).FirstOrDefault() == null)
        {
            await InitRoleCreate();
            await InitUser();
            db.App.Add(
                new App { Name = "Initialize", Val = "true" }
            );
            db.SaveChanges();
            return View();
        }
        return HttpNotFound();
    }

    private async Task InitRoleCreate()
    {
        var model = new List<string>()
    {
        "SysAdmin",
        "Admin",
        "User",
    };
        foreach (var item in model)
        {
            var role = new ApplicationRole(item);
            await RoleManager.CreateAsync(role);
        }
    }

    private async Task InitUser()
    {
        var user = new ApplicationUser
        {
            UserName = "HerGiz",
            Email = "hergiz@outlook.com",
            Name = "Igor Hermanović",
            Contact = "098 185 3131",
            TwoFactorEnabled = false,
            LockoutEnabled = true,
            EmailConfirmed = true
        };
        var adminResult = await UserManager.CreateAsync(user, "W7xtc2ywfb");
        await UserManager.AddToRolesAsync(user.Id, "SysAdmin");
    }
}

我需要的整个 API 部分 - 控制器和开箱即用的登录(在某处安装):

[Authorize]
public class ApiKeysController : ApiController
{
    [Authorize]
    [Route("api/getkey/{term}")]
    public ShowFullKeyVM Get(string term)
    {
        if (User.Identity.IsAuthenticated == true)
        {
            if (!string.IsNullOrWhiteSpace(term) && (term.Length == 15 || term.Length == 16))
            {
                var lKey = new LKey();
                var vm = lKey.Search(term);
                if (vm != null)
                {
                    return vm;
                }
            }
            return new ShowFullKeyVM() { Error = "IMEI either is not valid :(", SearchIMEI = term };
        }
        return new ShowFullKeyVM() { Error = "Not Authenticated!!!", SearchIMEI = term };
    }
}

Global.asax

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        GlobalConfiguration.Configure(WebApiConfig.Register);

        MvcHandler.DisableMvcResponseHeader = true;

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        App_Start.AutoMapperConfig.DefineMaps();

        ModelBinders.Binders.Add(typeof(decimal), new Extensions.DecimalModelBinder());
        ModelBinders.Binders.Add(typeof(decimal?), new Extensions.DecimalModelBinder());
    }

Web.config

<appSettings>
    <add key="UserLockoutEnabledByDefault" value="true" />
    <add key="DefaultAccountLockoutTimeSpan" value="30" />
    <add key="MaxFailedAccessAttemptsBeforeLockout" value="4" />
 </appSettings>