Loading

Core篇——初探Core的认证,授权机制

目录

1、Cookie-based认证的实现

2、Jwt Token 的认证与授权

3、Identity Authentication + EF 的认证

Cookie-based认证的实现

  cookie认证方式如下图所示,当我们访问一个网页(Admin/Index)时候,这时候系统会检查你是否有权限,假如没有权限,便会我当前Url重定向到登陆页面(/Account/Login),在登陆成功后,系统会返回一个cookie保存在浏览器,此时再带着这个cookie去重新访问你最开始要访问的页面(Admin/Index)。如下图所示。

我们在.net core 中,也有一套基于cookie-basic的认证方式。

  首先,创建一个Core 2.0的MVC项目,添加两个控制器AdminController代表我们要认证后才能访问的资源,AccountController,模拟登陆。在AccountController中,

 

1     [Authorize]
2     public class AdminController : Controller
3     {
4         public IActionResult Index()
5         {
6             return View();
7         }
8     }
AdminController

 

 1     public class AccountController : Controller
 2     {
 3         public async Task<IActionResult> MakeLogin()
 4         {
 5             var claims = new List<Claim>
 6             {
 7                 new Claim(ClaimTypes.Name,"lmc"),
 8                 new Claim(ClaimTypes.Role, "admin")
 9             };
10             var claimsIdentity = new ClaimsIdentity(
11                 claims,
12                 CookieAuthenticationDefaults.AuthenticationScheme
13                 );
14             await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
15                 new ClaimsPrincipal(claimsIdentity),
16                 new AuthenticationProperties {
17                     IsPersistent=true,                              //cookie过期时间设置为持久
18                     ExpiresUtc= DateTime.UtcNow.AddSeconds(20)      //设置过期20秒
19                 });
20             return Ok();
21         }
22         public async Task<IActionResult> Logout()
23         {
24             await HttpContext.SignOutAsync(
25     CookieAuthenticationDefaults.AuthenticationScheme);
26             return Ok();
27         }
28     }
AccountController

然后我们配置Startup,将认证服务加入到DI容器&&引用认证中间件。

 1 public void ConfigureServices(IServiceCollection services)
 2         {
 3             services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
 4                 .AddCookie(config=>
 5                 {
 6                     config.LoginPath = "/Account/MakeLogin";    //未认证导向登陆的页面,默认为/Account/Login
 7                     config.Cookie.Name = "lmccookie";           //设置一个cookieName
 8 
 9                 });
10             services.AddMvc();
11         }
12 
13  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
14         {
15             if (env.IsDevelopment())
16             {
17                 app.UseDeveloperExceptionPage();
18                 app.UseBrowserLink();
19             }
20             else
21             {
22                 app.UseExceptionHandler("/Home/Error");
23             }
24 
25             app.UseStaticFiles();
26             app.UseAuthentication(); //加入认证中间件
27                 ...//// other code
28         }    

测试下。 直接访问 Admin/Index被重定向到Login并返回我们定义的cookie,我们再带着cookie再次访问Admin/Index

 Jwt Token 的认证

  JwtToken 一般用于一些前后端分离的项目或者是移动端的项目。大体流程是用户首先访问目标资源(例如这里的api/values),然后服务器返回一个401或者是403的响应码标识未授权登陆。这时用户应该重新登陆获取token (例如这里的api/token),拿到token以后,请求头里面带着token再去访问目标资源。

  JwtToken 由三部分构成 首先是HEADER,这里面包含了Base64加密过的 加密算法和token的类型;PAYLOAD ,这里包含了一个Base64加密过的 Claims数组;SIGNATURE,包含了使用你的加密算法加密过后的 HEADER  ‘. ’ 和 PAYLOAD 加一个自定义的密钥。

  我们在.net core 中实现下Jwttoken的验证。

我们在ValuesController 打上[Authorize] 标签。配置我们的startup,在startup ConfigureServices方法中注入认证服务,Configure方法中添加中间件。 此时我们访问api/values=》返回401 的http状态码。

 

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings")); //appsettings中读取到jwtsettings节点
 4             var jwtSetting = new JwtSettings();
 5             Configuration.Bind("JwtSettings", jwtSetting);
 6             services.AddAuthentication(options =>
 7             {                                   // 添加认证头
 8                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 9                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
10             })
11             .AddJwtBearer(jo => jo.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
12             {
13                 ValidIssuer = jwtSetting.Issuer,                    //使用者
14                 ValidAudience = jwtSetting.Audience,                //颁发者
15                 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecreKey)) //加密方式
16             });
17             services.AddMvc();
18         }
19 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
20         {
21             if (env.IsDevelopment())
22             {
23                 app.UseDeveloperExceptionPage();
24             }
25             app.UseAuthentication();      //注意加入中间件
Startup
    public class JwtSettings
    {
        public string Issuer { get; set; }   //办法token的人
        public string Audience { get; set; } //token使用者
        public string SecreKey { get; set; } //token加密钥
    }
JwtSettings
 1 {
 2   "Logging": {
 3     "IncludeScopes": false,
 4     "Debug": {
 5       "LogLevel": {
 6         "Default": "Warning"
 7       }
 8     },
 9     "Console": {
10       "LogLevel": {
11         "Default": "Warning"
12       }
13     }
14   },
15   "JwtSettings": {
16     "Audience": "http://localhost:5000",
17     "Issuer": "http://localhost:5000",
18     "SecreKey": "HelloKeylmclmclmc"
19   }
20 }
appsettings

 

接下来需要生成token,添加一个AuthorizeController,通过构造函数把jwtsettings 注入进来,添加一个Index的Action 用来生成我们的token。我们需要一个验证下登录用户的用户名密码,所以还需要添加一个ViewModel

    public class LoginViewModel
    {
        [Required]
        public string Name { get; set; }
        [Required]
        public string PassWord { get; set; }
    }
LoginViewModel
 1         private JwtSettings _jwtSettings;
 2         public AuthorizeController(IOptions<JwtSettings> options)       //构造函数注入,拿到appsettings 里面的jwtsettings
 3         {
 4             _jwtSettings = options.Value;
 5         }
 6         [Route("api/token")]
 7         [HttpPost]
 8         public IActionResult Index(LoginViewModel loginViewModel)
 9         {
10             if (!ModelState.IsValid)
11                 return BadRequest();
12             if (!(loginViewModel.Name == "lmc" && loginViewModel.PassWord == "123456"))
13                 return BadRequest();
14             var claims = new Claim[]                //实例化一个Claim
15             {
16                 new Claim(ClaimTypes.Name,"lmc"),
17                 new Claim(ClaimTypes.Role, "admin")
18             };
19             var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecreKey));  //将appsettings里面的SecreKey拿到
20             var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);             //使用HmacSha256 算法加密
21             //生成token,设置过期时间为30分钟, 需要引用System.IdentityModel.Tokens.Jwt 包
22             var token = new JwtSecurityToken(_jwtSettings.Issuer, _jwtSettings.Audience, claims, DateTime.Now, DateTime.Now.AddMinutes(30), creds);
23             //将token返回
24             return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
25         }
AuthorizeController

一切准备就绪,拿到Token。带着token来访问 /api/values,注意token需要带这常量 bearer 。

带着我们的加密钥来jwt官网验证一下。 

基于角色(Role),Claim/Policy 的授权:

在我们返回token的时候,实例化过一个Claim数组,其中,Role是admin。我们修改ValuesController的Authorize特性标签( [Authorize(Roles = "user")])。

 

当我们带着token再去验证时候。然后我们把Claim数组的Role那一项的Value 改为user 便会正常认证。

基于Claim的验证我们需要做的事在StartUp的ConfigureServices方法中,将授权模块加入到DI容器中。这里我们添加了一个SuperAdminOnlydPolicy

此时,我们需要在AuthorizeController控制器返回Token的时候,需要在Claim数组中添加一个新的Claim,表示我们的Policy。

此时,再修改我们的ValuesController控制器的Authorize特性标签。

PostMan 走一波===》

 

 Identity +EF Authentication 的认证

  首先,使用net core 的脚手架命令创建一个自带Identity 的mvc项目。然后根据appseetings的数据库配初始化数据库(默认数据库实例是(localdb)\\mssqllocaldb,因为我装vs时候没装这个,所以我换成了.) 。

还原完了数据库,就可以把项目跑起来了,可以根据右上角注册,登录下==》  (其中代码可以自行观看)

然后让我们来自己从头开始实现下这个过程:

可以在上文中Cookie认证的项目中完成,也可以新建一个空的MVC core项目。添加一个Account控制器,其中存在三个方法(action),注册、登录,登出。

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Threading.Tasks;
  5 using Microsoft.AspNetCore.Mvc;
  6 using System.Security.Claims;
  7 using Microsoft.AspNetCore.Authentication.Cookies;
  8 using Microsoft.AspNetCore.Authentication;
  9 using MVCOnCookieBaseStudy.ViewModel;
 10 using Microsoft.AspNetCore.Identity;
 11 using MVCOnCookieBaseStudy.Models;
 12 
 13 namespace MVCOnCookieBaseStudy.Controllers
 14 {
 15     public class AccountController : Controller
 16     {
 17         private UserManager<ApplicationUser> _userManager; //加入Identity自带的注册使用的Manager
 18         private SignInManager<ApplicationUser> _signInManager; //加入Identity自带的登录使用的Manager
 19 
 20         public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
 21         {
 22             _userManager = userManager;
 23             _signInManager = signInManager;
 24         }
 25         /// <summary>
 26         /// 注册页面
 27         /// </summary>
 28         /// <returns></returns>
 29         public IActionResult Register(string returnUrl = "/Home/Index") 
 30         {
 31             ViewData["ReturnUrl"] = returnUrl;
 32             return View();
 33         }
 34         [HttpPost]
 35         public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = "/Home/Index")
 36         {
 37             ViewData["ReturnUrl"] = returnUrl;
 38             if (ModelState.IsValid)  //model 验证
 39             {
 40                 ApplicationUser identityUser = new ApplicationUser
 41                 {
 42                     Email = registerViewModel.Email,
 43                     UserName = registerViewModel.Email,
 44                     NormalizedUserName = registerViewModel.Email
 45                 };
 46                 var result = await _userManager.CreateAsync(identityUser, registerViewModel.Password);
 47                 if (result.Succeeded)
 48                 {
 49                     await _signInManager.SignInAsync(identityUser, new AuthenticationProperties { IsPersistent = true });
 50                     return Redirect(returnUrl);
 51                 }
 52                 else
 53                 {
 54                     foreach(var err in result.Errors)
 55                     {
 56                         ModelState.AddModelError("", err.Description);
 57                     }
 58                 }
 59             }
 60             
 61             return View();
 62         }
 63         /// <summary>
 64         /// 登录页面
 65         /// </summary>
 66         /// <returns></returns>
 67         public IActionResult Login(string returnUrl = "/Home/Index")
 68         {
 69             ViewData["ReturnUrl"] = returnUrl;
 70             return View();
 71         }
 72         [HttpPost]
 73         public async Task<IActionResult> Login(RegisterViewModel LoginViewModel, string returnUrl = "/Home/Index")
 74         {
 75             ViewData["ReturnUrl"] = returnUrl;
 76             var loginUser = await _userManager.FindByEmailAsync(LoginViewModel.Email);
 77             if (loginUser == null)
 78             {
 79                 return View();
 80             }
 81             
 82             await _signInManager.SignInAsync(loginUser, new AuthenticationProperties { IsPersistent = true });
 83             return Redirect(returnUrl);
 84         }
 85         /// <summary>
 86         /// 原来的Cookie登录
 87         /// </summary>
 88         /// <returns></returns>
 89         public async Task<IActionResult> MakeLogin()
 90         {
 91             var claims = new List<Claim>
 92             {
 93                 new Claim(ClaimTypes.Name,"lmc"),       
 94                 new Claim(ClaimTypes.Role, "admin")
 95             };
 96             var claimsIdentity = new ClaimsIdentity(
 97                 claims,
 98                 CookieAuthenticationDefaults.AuthenticationScheme
 99                 );
100             await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
101                 new ClaimsPrincipal(claimsIdentity),
102                 new AuthenticationProperties {
103                     IsPersistent=true,                              //cookie过期时间设置为持久
104                     ExpiresUtc= DateTime.UtcNow.AddSeconds(20)      //设置过期20秒
105                 });
106             return Ok();
107         }
108         /// <summary>
109         /// 登出
110         /// </summary>
111         /// <returns></returns>
112         public async Task<IActionResult> Logout()
113         {
114             await _signInManager.SignOutAsync();
115             return Redirect("/Home/Index");
116         }
117     }
118     //覆盖默认验证
119     public class MyCookieTestAuthorize: CookieAuthenticationEvents
120     {
121         public override Task ValidatePrincipal(CookieValidatePrincipalContext context)
122         {
123             return base.ValidatePrincipal(context);
124         }
125     }
126 }
AccountController
@using MVCOnCookieBaseStudy.ViewModel
@model RegisterViewModel
@{
    ViewData["Title"] = "Register";
}

<h2>@ViewData["Title"]</h2>

<div class="row">
    <div class="col-md-4">
        <form asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post">
            <h4>Create a new account.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Email"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ConfirmPassword"></label>
                <input asp-for="ConfirmPassword" class="form-control" />
                <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-default">Register</button>
        </form>
    </div>
</div>

@section Scripts {
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
注册的View
 1 @using MVCOnCookieBaseStudy.ViewModel
 2 @model RegisterViewModel
 3 @{
 4     ViewData["Title"] = "Login";
 5 }
 6 
 7 <h2>Login</h2>
 8 
 9 
10 <div class="row">
11     <div class="col-md-4">
12         <section>
13             <form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
14                 <h4>Use a local account to log in.</h4>
15                 <hr />
16                 <div asp-validation-summary="All" class="text-danger"></div>
17                 <div class="form-group">
18                     <label asp-for="Email"></label>
19                     <input asp-for="Email" class="form-control" />
20                     <span asp-validation-for="Email" class="text-danger"></span>
21                 </div>
22                 <div class="form-group">
23                     <label asp-for="Password"></label>
24                     <input asp-for="Password" class="form-control" />
25                     <span asp-validation-for="Password" class="text-danger"></span>
26                 </div>
27                 <div class="form-group">
28                     <button type="submit" class="btn btn-default">Log in</button>
29                 </div>
30             </form>
31         </section>
32     </div>
33 </div>
34 @section Scripts{ 
35     @await Html.PartialAsync("_ValidationScriptsPartial");  @*前端验证,需要引用jquery.validate.min.js*@
36 }
登录的View

这个过程中需要使用到一个ViewModel,用来传递登录数据

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel.DataAnnotations;
 4 using System.Linq;
 5 using System.Threading.Tasks;
 6 
 7 namespace MVCOnCookieBaseStudy.ViewModel
 8 {
 9     public class RegisterViewModel
10     {
11         [Required]
12         [EmailAddress]
13         [Display(Name = "Email")]
14         public string Email { get; set; }
15 
16         [Required]
17         [DataType(DataType.Password)]
18         [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
19         [Display(Name = "Password")]
20         public string Password { get; set; }
21 
22         [DataType(DataType.Password)]
23         [Display(Name = "Confirm password")]
24         [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
25         public string ConfirmPassword { get; set; }
26     }
27 }
RegisterViewModel

添加集成自Identity的User 和 Role还有数据库链接上下文

    public class ApplicationUserRole: IdentityRole
    {
    }
    public class ApplicationUser:IdentityUser
    {
    }
Role&&User
1     public class ApplicationDbContext:IdentityDbContext<ApplicationUser,ApplicationUserRole,string>
2     {
3         public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
4         {
5 
6         }
7     }
数据库链接上下文

接下来配置我们的StartUp,在ConfigureServices方法中 将数据库连接上下文加入DI容器,Identity服务加入DI容器,重新配置下我们的密码规则。(注意在Configure加入认证中间件)

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             //连接数据库服务加入DI容器
 4             services.AddDbContext<ApplicationDbContext>(option =>
 5             {
 6                 option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
 7             });
 8             //将Identity服务加入DI容器
 9             services.AddIdentity<ApplicationUser, ApplicationUserRole>()
10                 .AddEntityFrameworkStores<ApplicationDbContext>()
11                 .AddDefaultTokenProviders();
12             //设置注册密码的规则
13             services.Configure<IdentityOptions>(options =>
14             {
15                 options.Password.RequireLowercase = false;
16                 options.Password.RequireNonAlphanumeric = false;
17                 options.Password.RequireUppercase = false;
18             });
19 
20             services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
21                 .AddCookie(config=>
22                 {
23                     config.LoginPath = "/Account/Login";    //未认证导向登陆的页面,默认为/Account/Login
24                     config.Cookie.Name = "lmccookie";           //设置一个cookieName
25 
26                 });
27             services.AddMvc();
28         }
Startup ConfigServices
Jesse博客学习笔记。传送门=》 http://video.jessetalk.cn/
posted @ 2018-01-11 14:15  3WLineCode  阅读(1114)  评论(3编辑  收藏  举报