Reproduced in: https://www.cnblogs.com/liumengchen-boke/p/8243393.html
table of Contents
1, Cookie-based authentication implementation
2, Jwt Token authentication and authorization
3, the Identity Authentication + EF certification
Cookie-based authentication implementation
cookie authentication mode as shown below, when we visit a Web page (Admin / Index) time, this time the system will check whether you have permission, if you do not have permission, I will present Url redirected to the login page (/ Account / Login) , after the successful landing, the system will return a cookie stored in your browser, then re-took the cookie to re-visit the page you want to access the very beginning (Admin / Index). As shown below.
We .net core, but also based on a set of cookie-basic authentication method.
First, create a Core 2.0 of MVC project, add two controllers AdminController on behalf of the resources we have to be certified to access the rear, AccountController , simulated landing. In AccountController in
1 [Authorize] 2 public class AdminController : Controller 3 { 4 public IActionResult Index() 5 { 6 return View(); 7 } 8 }
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), {New new AuthenticationProperties 16 17 IsPersistent = true, // cookie set the expiration time lasting 18 ExpiresUtc = DateTime.UtcNow.AddSeconds (20) // set the expiration 20 seconds . 19}); 20 is return Ok (); 21 is} 22 is the async public the Task <IActionResult> Zimbabwe Logout () 23 is { 24 HttpContext.SignOutAsync the await ( 25 CookieAuthenticationDefaults.AuthenticationScheme); 26 is return Ok (); 27} 28}
Then we configure Startup, the authentication service is added to the DI container && certified reference middleware.
Public void ConfigureServices. 1 (IServiceCollection Services) 2 { . 3 services.AddAuthentication (CookieAuthenticationDefaults.AuthenticationScheme) . 4 .AddCookie (config => . 5 { . 6 config.LoginPath = "/ the Account / MakeLogin"; // unauthenticated guide landing pages, the default of / the Account / the Login . 7 config.Cookie.Name = "lmccookie"; // set a the cookieName . 8 . 9}); 10 services.AddMvc (); . 11} 12 is 13 is the Configure public void (App IApplicationBuilder, IHostingEnvironment the 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 }
Under the test. Direct access Admin / Index is redirected to the Login and return to our definition of the cookie, and then we took cookie access Admin / Index again
Jwt Token Authentication
JwtToken generally used for separating some of the front and rear ends of the movable end of the project or project. Processes are generally first user access target resource (e.g. where api / values), then the server returns a response code 401 or 403 identifying unauthorized login. Then the user should re-visit get token (eg api here / token), later got token, token request header to go inside with access to the target resource.
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(); //注意加入中间件
public class JwtSettings { public string Issuer { get; set; } //办法token的人 public string Audience { get; set; } //token使用者 public string SecreKey { get; set; } //token加密钥 }
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 }
接下来需要生成token,添加一个AuthorizeController,通过构造函数把jwtsettings 注入进来,添加一个Index的Action 用来生成我们的token。我们需要一个验证下登录用户的用户名密码,所以还需要添加一个ViewModel
public class LoginViewModel { [Required] public string Name { get; set; } [Required] public string PassWord { get; set; } }
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 }
一切准备就绪,拿到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容器中。这里我们添加了一个SuperAdminOnlyd的Policy
此时,我们需要在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 }
@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") }
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 }
这个过程中需要使用到一个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 }
添加集成自Identity的User 和 Role还有数据库链接上下文
public class ApplicationUserRole: IdentityRole { } public class ApplicationUser:IdentityUser { }
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 { Options.Password.RequireLowercase to false = 15; 16 = options.Password.RequireNonAlphanumeric to false; . 17 = options.Password.RequireUppercase to false; 18 is}); . 19 20 is services.AddAuthentication (CookieAuthenticationDefaults.AuthenticationScheme) 21 is .AddCookie (config => 22 is { 23 config.LoginPath = "/ Account / login "; // unauthenticated guide landing pages, the default is / the Account / the login 24 config.Cookie.Name = "lmccookie"; // set a the cookieName 25 26 is}); 27 Services .AddMvc (); 28}