• 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/
  • 相关阅读:
    Mybatis基础配置及增删查改操作
    SpringMVC注解方式与文件上传
    SpringMVC的基础配置及视图定位
    Spring AOP面向切面编程
    Spring注入属性、对象
    Spring的配置及jar包下载
    多线程
    集合框架
    I/O————流
    I/O————对象流
  • 原文地址:https://www.cnblogs.com/liumengchen-boke/p/8243393.html
Copyright © 2020-2023  润新知