目录
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 }


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 }
然后我们配置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(); //注意加入中间件


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 {
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 }



来源:oschina
链接:https://my.oschina.net/u/4312161/blog/4438787