Skip to content

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 中注册一系列的中间件。加入用户验证方面的功能

  1. 安装相关组件
  • Install-Package Microsoft.AspNet.Identity.EntityFramework
  • Install-Package Microsoft.AspNet.Identity.OWIN
  • Install-Package Microsoft.Owin.Host.SystemWeb
  1. 指定数据库连接及注册组件

web.Config 中注册数据库连接

注册: -->

或是注册:[assembly: OwinStartupAttribute(typeof(WebApplication1.Startup))]

  1. 创建 Entity Framework 相关类

public?class?AppUser:IdentityUser {} // 用户

public?class?AppIdentityDbContext : IdentityDbContext // ?Database Context

public?class?AppUserManager : UserManager {} // 用户管理

  1. 注册

    ```csharp

    app.CreatePerOwinContext(AppIdentityDbContext.Create); <-- 表示可以通过 HttpContext.GetOwinContext().Get(); 返回对应的对象 app.CreatePerOwinContext(AppUserManager.Create); app.UseCookieAuthentication // 配置相关参数 ```

  2. 使用

csharp // 控制器中使用 , 返回管理器 HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); UserManager.CreateAsync // 创建用户 // 以下更新用户密码 UserManager.PasswordValidator.ValidateAsync(password); user.PasswordHash = UserManager.PasswordHasher.HashPassword(password); UserManager.UpdateAsync(user);

程序流程

ASP.NET 表单身份验证与授权机制

一旦验证通过,将产生唯一的Cookie标识并输出到浏览器。来自浏览器的下一次请求将包含此Cookie。

  1. AuthenticateRequest 事件

FormsAuthenticationModule 模块 验证并解析该Cookie为对应的用户对象

  1. PostAuthenticateReques 事件

表示用户验证完成 , 设置 HttpContext.User.Identity.IsAuthenticated = true (验证过程)

  1. 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)
  1. Install-Package Microsoft.Owin.Security.Google

  2. 配置 API 参数

csharp app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() { ClientId = "165066370005-6nhsp87llelff3tou91hhktg6eqgr0ke.apps.googleusercontent.com", ClientSecret = "euWbCSUZujjQGKMqOyz0msbq", });

  1. 后台登录处理

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(); }

  1. 认证成功后访问 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 ?? "/"); // 进行跳转 } ```

与其它第三方整合(QQ、微信、微博)

ASP.NET MVC应用程序中支持用户使用腾讯QQ和微信以及新浪微博的第三方登录