ASP.NET MVC 登录认证
https://www.cnblogs.com/OceanEyes/category/696137.html
OWIN
OWIN
使用 OWIN 规范使用 .net web 可以脱离 iis。Katana 是 OWIN 的规范实现
OWIN定义了4层:
Host -> Server -> Middleware -> Appliation
Host:主要负责应用程序的配置和启动进程,包括初始化 OWIN Pipeline、运行Server。
Server:这是实际的Http Server,绑定套接字并监听的HTTP请求然后将Request和Response的Body、Header封装成符合OWIN规范的字典并发送到OWIN Middleware Pipeline中,最后Application为Response Data填充合适的字段输出。
Middleware:称之为中间件、组件,位于Server与Application之间,用来处理发送到Pipeline中的请求,这类组件可以是简单的Logger或者是复杂的Web Framework比如Web API、SignalR,只要Sever连接成功,Middleware中间件可以是任何实现应用程序委托的组件。
Application:这是具体的应用程序代码,可能在Web Framework之上。对于Web API、SignalR这类Web Framework中间件而言,我们仅仅是改变了他们的托管方式,而不是取代ASP.NET WEB API、SignalR原先的应用程序开发。所以该怎么开发就怎么开发,只不过我们将他们注册到OWIN Pipeline中去处理HTTP 请求,成为OWIN管道的一部分,所以此处的Application即正在意义上的处理程序代码。
Katana
Katana 是 OWIN 的规范实现 。它为我们提供了3中选择:
- IIS / ASP.NET :使用IIS是最简单和向后兼容方式,在这种场景中OWIN Pipeline通过标准的HttpModule和HttpHandler启动。使用此Host你必须使用System.Web作为OWIN Server
创建 Web Application, Install-Package Microsoft.Owin.Host.SystemWeb
- Custom Host :如果你想要使用其他Server来替换掉System.Web,并且可以有更多的控制权,那么你可以选择创建一个自定义宿主,如使用Windows Service、控制台应用程序、Winform来承载Server。
创建控制台应用程序, Install-Package Microsoft.Owin.SelfHost, WebApp.Start
("http://localhost:10002")
- OwinHost :如果你对上面两种Host还不满意,那么最后一个选择是使用Katana提供的OwinHost.exe:他是一个命令行应用程序,运行在项目的根部,启动HttpListener Server并找到基于约束的Startup启动项。OwinHost提供了命令行选项来自定义他的行为,比如:手动指定Startup启动项或者使用其他Server(如果你不需要默认的HttpListener Server)。
Install-Package OwinHost , 执行 OwinHost.exe
启动
- 默认名称约束:默认情况下Host会去查找root namespace下的名为Startup的类作为启动项。
- OwinStartup Attribute:当创建Owin Startup类时,自动会加上Attribute 如:[assembly: OwinStartup(typeof(JKXY.KatanaDemo.OwinHost.Startup))]
- 配置文件,如:
- 如果使用自定义Host,那么可以通过WebApp.Start
("http://localhost:10002") 来设置启动项。 - 如果使用OwinHost,那么可以通过命令行参数来实现,如下截图所示
ASP.NET Identity
http://www.cnblogs.com/OceanEyes/p/thinking-in-asp-net-mvc-get-started-with-identity.html
就是在 Middleware 中注册一系列的中间件。加入用户验证方面的功能
- 安装相关组件
- Install-Package Microsoft.AspNet.Identity.EntityFramework
- Install-Package Microsoft.AspNet.Identity.OWIN
- Install-Package Microsoft.Owin.Host.SystemWeb
- 指定数据库连接及注册组件
web.Config 中注册数据库连接
注册:
--> 或是注册:[assembly: OwinStartupAttribute(typeof(WebApplication1.Startup))]
- 创建 Entity Framework 相关类
public?class?AppUser:IdentityUser {} // 用户
public?class?AppIdentityDbContext : IdentityDbContext
// ?Database Context public?class?AppUserManager : UserManager
{} // 用户管理
-
注册
```csharp
app.CreatePerOwinContext
(AppIdentityDbContext.Create); <-- 表示可以通过 HttpContext.GetOwinContext().Get (); 返回对应的对象 app.CreatePerOwinContext (AppUserManager.Create); app.UseCookieAuthentication // 配置相关参数 ``` -
使用
csharp
// 控制器中使用 , 返回管理器
HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
UserManager.CreateAsync // 创建用户
// 以下更新用户密码
UserManager.PasswordValidator.ValidateAsync(password);
user.PasswordHash = UserManager.PasswordHasher.HashPassword(password);
UserManager.UpdateAsync(user);
程序流程
ASP.NET 表单身份验证与授权机制
一旦验证通过,将产生唯一的Cookie标识并输出到浏览器。来自浏览器的下一次请求将包含此Cookie。
- AuthenticateRequest 事件
FormsAuthenticationModule 模块 验证并解析该Cookie为对应的用户对象
- PostAuthenticateReques 事件
表示用户验证完成 , 设置 HttpContext.User.Identity.IsAuthenticated = true (验证过程)
- AuthorizeRequest 事件
UrlAuthorizationModule 模块访问 web.config 中的 authorization 段对当前用户是否可进行进行管理 (授权过程)
Katana进行身份验证
使用 Middleware 中间件完成 ,不再使用 FormsAuthenticationModule 模块
验证
```csharp // 注册中间件 app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login")}); 1. 将 CookieAuthenticationMiddleware 模块注册到 Middleware 的 AuthenticateRequest 事件中 2. 被调用后将用户信息添加到 OwinContext 中。 在控制器中可以通过 HttpContext.GetOwinContext().Request.User 获得用户信息
```
授权
// 使用 [Authorize] 属性进行授权
1. 验证失败返回 401.0 – Unauthorized
2. EndRequest 事件中被 302 重定向到登录链接 /Account/Login 中
登录
[AllowAnonymous] // <-- 允许任意用户访问
public ActionResult Login(string returnUrl)
{
if (HttpContext.User.Identity.IsAuthenticated){ return View("Error", new string[] {"您已经登录!"}); /* <-- // 已经登录 */ }
ViewBag.returnUrl = returnUrl;
// view 中最好加入 @Html.AntiForgeryToken()
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
AppUser user = await UserManager.FindAsync(model.Name, model.Password);
if (user == null)
{
ModelState.AddModelError("","无效的用户名或密码");
}
else
{
var claimsIdentity =
await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, claimsIdentity);
return Redirect(returnUrl);
}
}
ViewBag.returnUrl = returnUrl;
return View(model);
}
角色管理
[Authorize(Roles = "Administrator")] // 使用
public class AppRole:IdentityRole {} <-- 角色
public class AppRoleManager:RoleManager<AppRole> <-- 角色管理
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create); <-- 注册
await RoleManager.CreateAsync(new AppRole(name));
RoleManager.CreateAsync(new AppRole(name));
RoleManager.FindByIdAsync(id);
RoleManager.DeleteAsync
UserManager.AddToRoleAsync
UserManager.RemoveFromRoleAsync
初始化
因为系统最开始是没有用户 的。所以需要做初始化
public class IdentityDbInit : DropCreateDatabaseIfModelChanges<AppIdentityDbContext>
{
protected override void Seed(AppIdentityDbContext context)
{
PerformInitialSetup(context);
base.Seed(context);
}
public void PerformInitialSetup(AppIdentityDbContext context)
{
// 初始化
AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context));
AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context));
string roleName = "Administrators";
string userName = "Admin";
string password = "Password2015";
string email = "admin@jkxy.com";
if (!roleMgr.RoleExists(roleName))
{
roleMgr.Create(new AppRole(roleName));
}
AppUser user = userMgr.FindByName(userName);
if (user == null)
{
userMgr.Create(new AppUser { UserName = userName, Email = email }, password);
user = userMgr.FindByName(userName);
}
if (!userMgr.IsInRole(user.Id, roleName))
{
userMgr.AddToRole(user.Id, roleName);
}
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>{
static ApplicationDbContext()
{
// 设置被调用时需要初始化
System.Data.Entity.Database.SetInitializer(new IdentityDbInit());
}
}
#### 声明(Claim)
我认为声明就是用户的扩展属性
var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
claimsIdentity.Add(new Claim(ClaimTypes.PostalCode, "324300")); <-- 加入一个邮编声明
// 比如在 Login 时,生成用户后加入一系统相关的声明
claimsIdentity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
UpdateUserClaims(claimsIdentity); <-- 从数据库或第三方获取用户信息
AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, claimsIdentity);
动态设置权限 (设置角色声明)
// 可以编程动态添加角色声明 ,比如在
claims.Add(new Claim(ClaimTypes.Role, "BjStaff"));
这样就可以访问 [Authorize(Roles = "BjStaff")] 的页面了
动态设权限(使用授权过滤器)
```csharp // 声明 public class ClaimsAccessAttribute:AuthorizeAttribute { public string Issuer { get; set; } public string ClaimType { get; set; } public string Value { get; set; } protected override bool AuthorizeCore(HttpContextBase context) { return context.User.Identity.IsAuthenticated && context.User.Identity is ClaimsIdentity && ((ClaimsIdentity)context.User.Identity).HasClaim(x => x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value ); } }
// 使用, 只允许指定类型的角色访问 [ClaimsAccess(Issuer = "RemoteClaims", ClaimType = ClaimTypes.PostalCode, Value = "200000")] ```
使用第三方来身份验证(google)
-
Install-Package Microsoft.Owin.Security.Google
-
配置 API 参数
csharp
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() {
ClientId = "165066370005-6nhsp87llelff3tou91hhktg6eqgr0ke.apps.googleusercontent.com",
ClientSecret = "euWbCSUZujjQGKMqOyz0msbq",
});
- 后台登录处理
csharp
// 返回 HttpUnauthorizedResult, 访问第三方平台
[HttpPost]
[AllowAnonymous]
public ActionResult GoogleLogin(string returnUrl)
{
var properties = new AuthenticationProperties
{ // 设置认证成功后的返回地址
RedirectUri = Url.Action("GoogleLoginCallback", new { returnUrl = returnUrl })
};
HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");
return new HttpUnauthorizedResult();
}
-
认证成功后访问 callback 地址
```csharp ///
/// Google登陆成功后(即授权成功)回掉此Action /// /// ///[AllowAnonymous] public async Task GoogleLoginCallback(string returnUrl) { ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync(); AppUser user = await UserManager.FindAsync(loginInfo.Login); if (user == null) { // 如果用户以前没有保存过,则新建用户并保存 user = new AppUser { Email = loginInfo.Email, UserName = loginInfo.DefaultUserName, City = Cities.Shanghai, Country = Countries.China }; IdentityResult result = await UserManager.CreateAsync(user); if (!result.Succeeded) { return View("Error", result.Errors); } result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login); if (!result.Succeeded) { return View("Error", result.Errors); }
}
// 创建凭证 ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(loginInfo.ExternalIdentity.Claims); // 获取第三方的声明 AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); return Redirect(returnUrl ?? "/"); // 进行跳转 } ```