我已经查看了许多地方,这里有一些与WIF和WCF相关的示例代码,在MVC项目中使用WCF服务(我可以轻松地做)但是似乎没有什么特别的AccountController.cs在MVC项目中开箱即用的功能,并通过WCF模拟/提供这些操作作为服务。主要是用户认证。
我想使用WCF服务将Web应用程序层与我的数据库层(经典3层架构)完全分开。我见过的所有指南似乎都暗示必须制作自己的自定义PasswordValidator和/或修改UserStores或滚动自己的自定义身份验证方案(是吗?我错过了更简单的东西吗?)。
似乎会有一种直接的方式来生成或启用在WCF项目中的MVC项目开始时生成的相同ASP.NET实体框架,但我不明白如何绑定ASP。 Net Identity授权,就像它在MVC项目中用于WCF服务一样。如何使服务使用MVC站点创建和填充的身份数据库授权用户凭据?
这基本上就是我遇到的麻烦。在我能够工作之后,我想将服务引用添加到Web应用程序并使用这些服务请求通过我的控制器方法加载任意数据并在Views中使用它们,但是那部分我知道该怎么做,我已经之前做过。
我看过的例子。 https://msdn.microsoft.com/en-us/library/ff647503.aspx http://www.codeproject.com/Articles/802435/Authentication-and-Authorization-with-ASP-NET-Iden
我正处于这样的地步,我愿意放弃尝试找到这样做的直接方式,只是实现我自己的身份验证,将散列密码存储在一个表中,其中包含将用户密码与他们的密码进行比较所需的盐输入并绕过此问题。这将意味着重新发明轮子然后重新执行所有AccountController.cs方法和IdentityConfig.cs,并弄清楚如何配置web.config以允许自定义身份验证。我认为这些VisualStudio工具和框架的重点不在于此。
我有什么遗漏或误解吗?我应该创建自己的自定义身份验证提供程序吗?
非常感谢任何帮助。我还看到了有关WCF的STS和WIF的文章,但我认为这不是我现在所追求的。
答案 0 :(得分:2)
我最终实现了自己的IdentityUser类和接口。然后使用它作为基础在我的应用程序中编写自定义UserStore,UserTable,ApplicationUserManager和AppPassword类,这些类利用对WCF服务的引用来获取必要的数据
using System;
using System.Data;
using System.Data.SqlClient;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using System.Security.Cryptography;
using System.Text;
namespace ProudSourcePrime.Identity
{
/// <summary>
/// Class that represents the Users table in our database.
///
/// It actually does not connect to our database anymore.
///
/// The data it recives for this implementation comes from a service reference running on our data service server.
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class UserTable<TUser> where TUser : IdentityUser
{
/// <summary>
/// sql query that will retrive the username of this user account, keying off of the userId
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public string GetUserName(string userId)
{
string userName = null;
// TODO : sql query that will retrive the username of this user account, keying off of the userId
return userName;
}
/// <summary>
/// sql query that will retrive the userId of this user account, keying off of the userName
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public string GetUserId(string userName)
{
string userId = null;
// TODO : sql query that will retrive the userId of this user account, keying off of the userName
return userId;
}
/// <summary>
/// sql query to get our user data
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public TUser GetUserById(string userId)
{
TUser user = null;
ServiceReference1.Service1Client ProudSoureService = new ServiceReference1.Service1Client();
ServiceReference1.UserRecordComposite userComposite = ProudSoureService.get_UserById(userId);
user = (TUser)Activator.CreateInstance(typeof(TUser));
user.AccessFailedCount = userComposite.AccessFailedCount;
user.Email = userComposite.Email;
user.Id = userComposite.Id;
user.LockoutEnabled = userComposite.LockoutEndDateUtc;
user.Name = userComposite.Name;
user.PasswordHash = userComposite.PasswordHash;
user.PhoneNumber = userComposite.PhoneNumber;
user.PhoneNumberConfirmed = userComposite.PhoneNumberConfirmed;
user.SecurityStamp = userComposite.SecurityStamp;
user.TwoFactorEnabled = userComposite.TwoFactorEnabled;
user.UserName = userComposite.UserName;
return user;
}
/// <summary>
/// sql query to retrive user using username
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public TUser GetUserByUserName(string userName)
{
TUser user = null;
ServiceReference1.Service1Client ProudSourceService = new ServiceReference1.Service1Client();
ServiceReference1.UserRecordComposite userComposite = ProudSourceService.get_UserByUserName(userName);
user = (TUser)Activator.CreateInstance(typeof(TUser));
user.AccessFailedCount = userComposite.AccessFailedCount;
user.Email = userComposite.Email;
user.Id = userComposite.Id;
user.LockoutEnabled = userComposite.LockoutEndDateUtc;
user.Name = userComposite.Name;
user.PasswordHash = userComposite.PasswordHash;
user.PhoneNumber = userComposite.PhoneNumber;
user.PhoneNumberConfirmed = userComposite.PhoneNumberConfirmed;
user.SecurityStamp = userComposite.SecurityStamp;
user.TwoFactorEnabled = userComposite.TwoFactorEnabled;
user.UserName = userComposite.UserName;
return user;
}
/// <summary>
/// sql query for password hash of this user keying off of the userId
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public string GetPasswordHash(string userId)
{
return new ServiceReference1.Service1Client().get_PasswordHash(userId);
}
/// <summary>
/// Sql command that actually created the user record and inserts into it the passwordHash, usernam and Guid Id.
/// </summary>
/// <param name="user"></param>
/// <param name="passwordHash"></param>
/// <returns></returns>
public bool SetPasswordHash(TUser user, string passwordHash)
{
return new ServiceReference1.Service1Client().set_PasswordHash(user.Id, passwordHash, user.UserName, user.Name);
}
/// <summary>
/// Sql command that actually creates the user record and inserts into it the passwordHash, and Guid Id.
///
/// No in use currently SetPasswordHash(TUser user, string passwordHash) gets called.
/// </summary>
/// <param name="userId"></param>
/// <param name="passwordHash"></param>
/// <returns></returns>
public bool SetPasswordHash(string userId, string passwordHash)
{
bool result = false;
//
return result;
}
/// <summary>
/// sql query that retrives the security stamp for this user record
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public string GetSecurityStamp(string userId)
{
string securityStamp = null;
// TODO : sql query that retrives the security stamp for this user record
return securityStamp;
}
/// <summary>
/// sql query that will update a Users table record with the given security stamp
/// </summary>
/// <param name="userId"></param>
/// <param name="securityStamp"></param>
/// <returns></returns>
public bool SetSecurityStamp(string userId, string securityStamp)
{
bool result = false;
// TODO : sql query that sets the security stamp of a user record
return result;
}
/// <summary>
/// sql query that inserts a new user into our data base
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public bool Insert(TUser user)
{
bool result = false;
// TODO : sql query that inserts a new User entry
return result;
}
/// <summary>
/// sql query to delete this user from our table
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public bool Delete(TUser user)
{
bool result = false;
// TODO : sql query to delete this user from our table
return result;
}
/// <summary>
/// sql query that will update our user record on our Users table
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public bool Update(TUser user)
{
bool result = false;
// TODO : sql query that will update our user record on our Users table
return result;
}
}
/// <summary>
/// Class that implements ASP.NET IUserPasswordStore, IUserSecurityStamp and IUserStore off of the concrete impemntations of IdentityUser's methods.
/// </summary>
/// <typeparam name="TUser"></typeparam>
public class UserStore<TUser> : IUserStore<TUser>, IUserPasswordStore<TUser> where TUser : IdentityUser
{
/// <summary>
/// Private resident that gives access to UserTable's concrete methods
/// </summary>
private UserTable<TUser> userTable;
/// <summary>
/// Default Constructor that initializes a new UserTable with connection to our data base.
/// </summary>
public UserStore()
{
userTable = new UserTable<TUser>();
}
/// <summary>
/// Insert a new user into our Users table.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task CreateAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
// This is commented out for now
//
// New User is actually created in method SetPasswordHash(TUser user, string passwordHash).
//userTable.Insert(user);
return Task.FromResult<object>(null);
}
/// <summary>
/// Delete a user from our Users table.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task DeleteAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
userTable.Delete(user);
return Task.FromResult<object>(null);
}
void IDisposable.Dispose()
{
throw new NotImplementedException();
}
/// <summary>
/// Retrives a user by using an Id.
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public Task<TUser> FindByIdAsync(string userId)
{
if (string.IsNullOrEmpty(userId))
{
throw new ArgumentNullException("user");
}
return Task.FromResult(userTable.GetUserById(userId));
}
/// <summary>
/// Find a user by using the username.
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public Task<TUser> FindByNameAsync(string userName)
{
if (string.IsNullOrEmpty(userName))
{
throw new ArgumentNullException("TUser is null");
}
return Task.FromResult(userTable.GetUserByUserName(userName));
}
/// <summary>
/// Returns the passwordhash for a given TUser
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<string> GetPasswordHashAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(userTable.GetPasswordHash(user.Id));
}
/// <summary>
/// Get the security stamp for a given TUser.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<string> GetSecurityStampAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(userTable.GetSecurityStamp(user.Id));
}
/// <summary>
/// Verfies whether a given TUser has a password.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task<bool> HasPasswordAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if (!string.IsNullOrEmpty(userTable.GetPasswordHash(user.Id)))
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(false);
}
}
/// <summary>
/// Sets the password hash for a given TUser.
/// </summary>
/// <param name="user"></param>
/// <param name="passwordHash"></param>
/// <returns></returns>
public Task SetPasswordHashAsync(TUser user, string passwordHash)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if(userTable.SetPasswordHash(user, passwordHash))
{
return Task.FromResult("true");
}
else
{
return Task.FromResult("false");
}
}
/// <summary>
/// This method will set the secrity stamp for a given TUser.
/// </summary>
/// <param name="user"></param>
/// <param name="stamp"></param>
/// <returns></returns>
public Task SetSecurityStampAsync(TUser user, string stamp)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if(userTable.SetSecurityStamp(user.Id, stamp))
{
return Task.FromResult("true");
}
else
{
return Task.FromResult("false");
}
}
/// <summary>
/// This method will update a given TUser
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public Task UpdateAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if(userTable.Update(user))
{
return Task.FromResult("true");
}
else
{
return Task.FromResult("false");
}
}
}
/// <summary>
/// Implementation of the UserManager class that will be handeling verification and
/// </summary>
public class ApplicationUserManager : UserManager<IdentityUser>
{
/// <summary>
/// Class instantiation.
/// </summary>
/// <param name="store"></param>
public ApplicationUserManager(UserStore<IdentityUser> store) : base(store)
{
Store = store;
this.PasswordHasher = new AppPassword();
UserLockoutEnabledByDefault = false;
// this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(10);
// this.MaxFailedAccessAttemptsBeforeLockout = 10;
UserValidator = new UserValidator<IdentityUser>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
}
/// <summary>
/// Override that actually uses the base layer implementation thus exposing it's funtionality through this object.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public override System.Threading.Tasks.Task<IdentityResult> CreateAsync(IdentityUser user, string password)
{
return base.CreateAsync(user, password);
}
}
/// <summary>
/// Custom password hasher and hash comparison implementor.
/// </summary>
public class AppPassword : IPasswordHasher
{
/// <summary>
/// Method that hashes passwords.
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
public string HashPassword(string password)
{
using (SHA256 sha = SHA256Managed.Create())
{
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(password.ToString()));
StringBuilder hashSB = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
hashSB.Append(hash[i].ToString("x2"));
}
return hashSB.ToString();
}
}
/// <summary>
/// Method that compares a given password hash with an input password and compares the given password hash with the hash of the input password.
/// </summary>
/// <param name="hashedPassword"></param>
/// <param name="providedPassword"></param>
/// <returns></returns>
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
string providedPassword_hashed = HashPassword(providedPassword);
if (hashedPassword.Equals(providedPassword_hashed))
{
return PasswordVerificationResult.Success;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
}
这里是IdentityUser
using Microsoft.AspNet.Identity;
using System;
namespace ProudSourcePrime.Identity
{
/// <summary>
/// Class that implements ASP.NET Identity IUser interface.
/// </summary>
public class IdentityUser : IUser
{
public string Id { get; set; }
public string UserName { get; set; }
public virtual string Email { get; set; }
public virtual string PasswordHash { get; set; }
public virtual string SecurityStamp { get; set; }
public virtual string PhoneNumber { get; set; }
public virtual bool PhoneNumberConfirmed { get; set; }
public virtual bool TwoFactorEnabled { get; set; }
public virtual DateTime? LockoutEnabled { get; set; }
public virtual int AccessFailedCount { get; set; }
public virtual string Name { get; set; }
/// <summary>
/// Default constructor that generates a new guid.
/// </summary>
public IdentityUser()
{
Id = Guid.NewGuid().ToString();
}
/// <summary>
/// Constructor that accepts a Username as a parameter.
/// </summary>
/// <param name="userName"></param>
public IdentityUser(string userName) : this()
{
UserName = userName;
}
/// <summary>
/// Public accessor to this IdentityUsers GUID
/// </summary>
string IUser<string>.Id
{
get
{
return Id;
}
}
/// <summary>
/// Public accessor to this IdentityUsers UserName
/// </summary>
string IUser<string>.UserName
{
get
{
return UserName;
}
set
{
UserName = value;
}
}
}
}
然后我实现了AppUserPrincipal,它继承了ClaimsPrincipal以授权声明,AppViewPage继承了WebViewPage,因此网站上的所有页面现在都是AppViewPage类型,但仍然继承了正确运行所需的内容
using System.Security.Claims;
using System.Web.Mvc;
namespace ProudSourcePrime.Config
{
public class AppUserPrincipal : ClaimsPrincipal
{
public AppUserPrincipal(ClaimsPrincipal principal) : base(principal)
{
}
public string Name
{
get
{
return this.FindFirst(ClaimTypes.Name).Value;
}
}
}
public abstract class AppController : Controller
{
public AppUserPrincipal CurrentUser
{
get
{
return new AppUserPrincipal(this.User as ClaimsPrincipal);
}
}
}
/// <summary>
/// Custom base view page to be inherited by all Razor Views in the web application.
///
/// This provides access too our AppUser principal.
/// </summary>
/// <typeparam name="TModel"></typeparam>
public abstract class AppViewPage<TModel> : WebViewPage<TModel>
{
protected AppUserPrincipal CurrentUser
{
get
{
return new AppUserPrincipal(this.User as ClaimsPrincipal);
}
}
}
public abstract class AppViewPage : AppViewPage<dynamic>
{
}
}
然后我在根目录中创建了一个web.config,但实际上是在MVC项目的Views文件夹中,我在那里配置网站以使用我的AppViewPage类
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<!--<pages pageBaseType="System.Web.Mvc.WebViewPage">-->
<pages pageBaseType="ProudSourcePrime.Config.AppViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.Optimization "/>
<add namespace="ProudSourcePrime" />
</namespaces>
</pages>
</system.web.webPages.razor>
<appSettings>
<add key="webpages:Enabled" value="false" />
<add key="owin:AppStartup" value="Startup"/>
</appSettings>
<system.webServer>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
<system.web>
<compilation>
<assemblies>
<add assembly="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
</system.web>
</configuration>
然后我必须确保将我的服务引用添加到项目中,并确保该服务返回必要的数据,然后准备就绪。
这是我的github上项目的链接。在你去哦哦之前,这里有内部IP和感知信息!我知道并且我不关心它所处的服务器环境是否与任何不通过面向公众的网络服务器的变化密切相关,并且该网络服务器是唯一可以从开放网络访问的东西。即使我不再访问,我故意这样做,以便没有人可以访问它,它很快就会消失。
这个项目是为了一个finTech创业公司,现在已经不复存在,因为三位创始人(我和其他两位)决定冒险进入其他领域。
如果你想看到最终结果,虽然去这里,它会很快消失,因为我停止支付帐户。 http://proudsource.us/welcome