ASP.Net-Core 中的自定义认证

我的工作是一个网络应用程序,需要与现有的用户数据库集成。我仍然希望使用 [Authorize]属性,但是我不想使用 Identity 框架。如果我确实想使用 Identity 框架,我会在 startup.cs 文件中添加如下内容:

services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireNonLetterOrDigit = false;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

我假设我必须在那里添加一些其他的东西,然后创建某种类来实现一个特定的接口?谁能告诉我正确的方向?我正在使用 asp.net 5的 RC1。

133476 次浏览

Creating custom authentication in ASP.NET Core can be done in a variety of ways. If you want to build off existing components (but don't want to use identity), checkout the "Security" category of docs on docs.asp.net. https://docs.asp.net/en/latest/security/index.html

Some articles you might find helpful:

Using Cookie Middleware without ASP.NET Identity

Custom Policy-Based Authorization

And of course, if that fails or docs aren't clear enough, the source code is at https://github.com/dotnet/aspnetcore/tree/master/src/Security which includes some samples.

From what I learned after several days of research, Here is the Guide for ASP .Net Core MVC 2.x Custom User Authentication

In Startup.cs :

Add below lines to ConfigureServices method :

public void ConfigureServices(IServiceCollection services)
{


services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme
).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
});


services.AddMvc();


// authentication
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});


services.AddTransient(
m => new UserManager(
Configuration
.GetValue<string>(
DEFAULT_CONNECTIONSTRING //this is a string constant
)
)
);
services.AddDistributedMemoryCache();
}

keep in mind that in above code we said that if any unauthenticated user requests an action which is annotated with [Authorize] , they well force redirect to /Account/Login url.

Add below lines to Configure method :

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler(ERROR_URL);
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: DEFAULT_ROUTING);
});
}

Create your UserManager class that will also manage login and logout. it should look like below snippet (note that i'm using dapper):

public class UserManager
{
string _connectionString;


public UserManager(string connectionString)
{
_connectionString = connectionString;
}


public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)
{
using (var con = new SqlConnection(_connectionString))
{
var queryString = "sp_user_login";
var dbUserData = con.Query<UserDbModel>(
queryString,
new
{
UserEmail = user.UserEmail,
UserPassword = user.UserPassword,
UserCellphone = user.UserCellphone
},
commandType: CommandType.StoredProcedure
).FirstOrDefault();


ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);


await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
}
}


public async void SignOut(HttpContext httpContext)
{
await httpContext.SignOutAsync();
}


private IEnumerable<Claim> GetUserClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();


claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserFirstName));
claims.Add(new Claim(ClaimTypes.Email, user.UserEmail));
claims.AddRange(this.GetUserRoleClaims(user));
return claims;
}


private IEnumerable<Claim> GetUserRoleClaims(UserDbModel user)
{
List<Claim> claims = new List<Claim>();


claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id().ToString()));
claims.Add(new Claim(ClaimTypes.Role, user.UserPermissionType.ToString()));
return claims;
}
}

Then maybe you have an AccountController which has a Login Action that should look like below :

public class AccountController : Controller
{
UserManager _userManager;


public AccountController(UserManager userManager)
{
_userManager = userManager;
}


[HttpPost]
public IActionResult LogIn(LogInViewModel form)
{
if (!ModelState.IsValid)
return View(form);
try
{
//authenticate
var user = new UserDbModel()
{
UserEmail = form.Email,
UserCellphone = form.Cellphone,
UserPassword = form.Password
};
_userManager.SignIn(this.HttpContext, user);
return RedirectToAction("Search", "Home", null);
}
catch (Exception ex)
{
ModelState.AddModelError("summary", ex.Message);
return View(form);
}
}
}

Now you are able to use [Authorize] annotation on any Action or Controller.

Feel free to comment any questions or bug's.

@Manish Jain, I suggest to implement the method with boolean return:

public class UserManager
{


// Additional code here...


public async Task<bool> SignIn(HttpContext httpContext, UserDbModel user)
{
// Additional code here...


// Here the real authentication against a DB or Web Services or whatever
if (user.Email != null)
return false;


ClaimsIdentity identity = new ClaimsIdentity(this.GetUserClaims(dbUserData), CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(identity);


// This is for give the authentication cookie to the user when authentication condition was met
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return true;
}
}

I would like to add something to brilliant @AmiNadimi answer for everyone who going implement his solution in .NET Core 3:

First of all, you should change signature of SignIn method in UserManager class from:

public async void SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)

to:

public async Task SignIn(HttpContext httpContext, UserDbModel user, bool isPersistent = false)

It's because you should never use async void, especially if you work with HttpContext. Source: Microsoft Docs

The last, but not least, your Configure() method in Startup.cs should contains app.UseAuthorization and app.UseAuthentication in proper order:

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}


app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});