不用不知道,一用香到爆。
老哥是個屌絲前端,但也想寫點web應用耍一耍。之前弄過了NodeJs,也弄過JAVA,最近由於寫游戲的原因用C#,索性上手一波asp.net core。
這篇博客記錄的是,如何在分分鍾內搭建一個博客程序。包括:
- 發博客
- 看博客
- 用戶注冊登錄
- 用戶權限設置。
其中用的就是微軟提供的EntityFrame和Identity類庫。簡直他媽爽出翔。
1.快速生成一個項目
反正增刪改查就那么回事兒,快速生成一個項目模板才是主要的。
我不想創建一個帶Razor頁面的項目。因為我只需要API。老夫可是個前端!
這個時候按F5運行網站,然后就可以用Postman向 http://localhost:55536/api/values發送請求了。如果有過開發經驗的人一眼就能看明白這是怎么回事兒。
2.給程序添加用戶系統。
添加用戶系統的意思是,允許用戶注冊和登錄。
如果我用NodeJs或者Java,我就要開始寫數據庫了,甚至設計數據表。可是微軟已經把好用的東西給准備好了,那就是:Identity類庫。這個類庫老JB好了。我只需要輕輕點幾下,一套完備的用戶系統就能生成到我的代碼上。
我現在來解釋一波我進行了什么操作。
1.剛才我加的一大堆東西,其實就是最開始創建項目的時候,“身份驗證”那一部分幫我們做的事。當時我沒選,現在我手動加上。
2.上面這個圖,“替代所有文件”這部分如果選中,框架會幫我們生成相應的業務邏輯和Html模板(當然是Razor模板)。
3.因為注冊登錄需要和數據庫交互,所以“新建數據庫上下文類”幫我們新生成了一個和數據庫交互的上下文類。這個類是EntityFramework提供的。巨牛逼巨方便。
4.“新建用戶類”,這沒什么好說的吧?這個用戶類用於和數據庫的用戶表進行對應。
這下我們牛逼了。然后你會發現項目目錄里多了一些文件,這些都是asp.net core幫我們生成的。
可以隨便探索一下。那個Readme.txt文件可以讀一下。是一個指導手冊,告訴你接下來要怎么做。
3.那么接下來要怎么做?
如readme文件所說,一步一步來。我還是貼出來readme文件:
Support for ASP.NET Core Identity was added to your project - The code for adding Identity to your project was generated under Areas/Identity. Configuration of the Identity related services can be found in the Areas/Identity/IdentityHostingStartup.cs file. If your app was previously configured to use Identity, then you should remove the call to the AddIdentity method from your ConfigureServices method. //生成的UI需要靜態文件支持,用下面這段代碼使你的app支持靜態文件 The generated UI requires support for static files. To add static files to your app: 1. Call app.UseStaticFiles() from your Configure method //用下面這段代碼開啟身份認證功能 To use ASP.NET Core Identity you also need to enable authentication. To authentication to your app: 1. Call app.UseAuthentication() from your Configure method (after static files) //生成的UI需要MVC支持,用這面這段代碼開啟MVC功能 The generated UI requires MVC. To add MVC to your app: 1. Call services.AddMvc() from your ConfigureServices method 2. Call app.UseMvc() from your Configure method (after authentication) //生成的數據庫結構需要你執行Migration來同步數據庫 The generated database code requires Entity Framework Core Migrations. Run the following commands:
//在cmd中執行下面兩個命令 1. dotnet ef migrations add CreateIdentitySchema 2. dotnet ef database update
//或者 在包管理命令行執行下面兩個命令 Or from the Visual Studio Package Manager Console: 1. Add-Migration CreateIdentitySchema 2. Update-Database Apps that use ASP.NET Core Identity should also use HTTPS. To enable HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
按照上面的操作來擼好app以后。框架就搭成了。牛逼到爆。一行代碼都沒寫,一個非常完備的基礎架子已經OK了。
需要注意的是,你要額外安裝EntityFramework類庫。這個百度教程太多了。我就不說了。
當你執行完那兩個命令后,你會發現你的數據庫里多了一些表。酷!成功了。
注:在這里執行命令的時候可能會說EntityFramework沒安裝什么的這時候不要虛,仔細看輸出,會說你裝了EF6和EFCore,你要指定一下用哪個EF來運行命令,asp.net core的話就用 EntityFrameworkCore\Update-Database
4.寫一波注冊用戶的API吧!
其實注冊的業務邏輯已經生成好了,直接拿來用就可以。去Areas/Identity/Pages/Account/Register.cshtml里面,可以看到這段代碼。稍微改動一下就可以拿來用了。
首先是,我打算用Postman模擬用戶前端的輸入,后端在注冊的時候接收3個值,郵箱,用戶名,密碼。於是俺創建一個類代表這個數據格式。強類型語言就是爽。
public class UserRegisterInput { public string UserName { get; set; } public string Email { get; set; } public string Password { get; set; } public bool RememberMe { get; set; } }
現在開始寫controller。新建一個APIController,這個簡直不用再描述了。最后Controller代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using TinyBlog2.Areas.Identity.Data; using TinyBlog2.DTO; namespace TinyBlog2.Controllers { [Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { private readonly UserManager<TinyBlog2User> _userManager; public UserController(UserManager<TinyBlog2User> userManager) { _userManager = userManager; } [HttpPost] [Route("Reg")] //這里是你的路由地址 post發往 https://localhost:55683/api/user/reg public async Task<IActionResult> Post([FromBody] UserRegisterInput Input) { var user = new TinyBlog2User { UserName = Input.UserName, Email = Input.Email }; var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { return Ok("注冊成功"); } else { return BadRequest(); } } [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; } } }
然后我用Postman發一波請求試試。
注冊的最后,查看數據庫,你的用戶顯然已經存在數據庫里了。這些數據表都是asp.net core + EntityFramework給我們建立好的。
5.用戶權限校驗
接下來我要做的是,給某個Action增加權限校驗,說句白話就是,有的接口我希望登錄用戶才能訪問,有的接口我希望管理員才能訪問,或者有的接口我希望只有付費Vip才能訪問。怎么做呢?
這里用已經存在的ViewController來舉例子。目前為止,ValueController的數據是誰都可以訪問的。但是我來加一行代碼,就一行!
[HttpGet] [Authorize(Policy = "VipOnly")]//很明顯,從此這個Action只能是Vip才能訪問。 public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; }
顯然我定制了一個策略,這個策略名字叫做VipOnly。那么接下來我要定義這個策略。
public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy("VipOnly", policy => policy.RequireClaim("Role", "VipUser")); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
我在startup.cs里面加入這一行代碼,應該顯而易見。意思是:增加一個名為VipOnly的策略,這個策略的要求是,如果用戶有一個屬性Role,這個Role的值是VipUser,那么就符合這個策略。
Claim就是用戶的一個屬性。這個屬性可以在任何時候創建。這個Claim也是Asp.net core提供給我們的工具!很方便。來看一波代碼吧。
5.1 如何給用戶添加一個Claim
我更改了一下注冊流程,每一個注冊用戶都被默認設置為VipUser
[HttpPost] [Route("Reg")] public async Task<IActionResult> Post([FromBody] UserRegisterInput Input) { var user = new TinyBlog2User { UserName = Input.UserName, Email = Input.Email }; var result = await _userManager.CreateAsync(user, Input.Password); //給用戶增加一個Claim var addClaimResult = await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("Role", "VipUser")); if (result.Succeeded) { return Ok("注冊成功"); } else { return BadRequest(); } }
簡單到爆炸不是嗎?我現在注冊一個用戶,就會看到這個用戶被添加了一個Claim,Role=VipUser
6.用戶登錄
用戶登錄的原理是JWT。是一個獨立知識點。這里我只提供代碼。教程網上一堆。需要我寫的話請留言,我再補充。

using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using TinyBlog2.Areas.Identity.Data; using TinyBlog2.DTO; namespace TinyBlog2.Controllers { [Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { private readonly UserManager<TinyBlog2User> _userManager; private readonly IConfiguration _config; private readonly SignInManager<TinyBlog2User> _signInManager; public UserController(UserManager<TinyBlog2User> userManager, IConfiguration configuration, SignInManager<TinyBlog2User> signInManager) { _config = configuration; _signInManager = signInManager; _userManager = userManager; } [HttpPost] [Route("Reg")] public async Task<IActionResult> Post([FromBody] UserRegisterInput Input) { var user = new TinyBlog2User { UserName = Input.UserName, Email = Input.Email }; var result = await _userManager.CreateAsync(user, Input.Password); //給用戶增加一個Claim var addClaimResult = await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("Role", "VipUser")); if (result.Succeeded && addClaimResult.Succeeded) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); //將加密后的密碼用JWT指定算法進行加密 var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); //拿到當前登錄用戶 TinyBlog2User currentUser = await _userManager.FindByEmailAsync(Input.Email); //獲取當前用戶的Claims IList<Claim> claimsList = await _userManager.GetClaimsAsync(currentUser); var unSecruityToken = new JwtSecurityToken(_config["Jwt:Issuer"], _config["Jwt:Issuer"], claimsList, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); var token = new JwtSecurityTokenHandler().WriteToken(unSecruityToken); return Ok(new { user = user, token = token }); } else { return BadRequest(); } } /// <summary> /// 前后端分離,前端的登錄請求發送到這里。 /// 返回200或者401,代表登錄成功和失敗,如果登錄成功,返回一個token。 /// </summary> /// <param name="inputUser"></param> /// <returns> /// {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IjFAMS5jb20iLCJqdGkiOiI0ZDNiZGFjMC1hNjYzLTQwNTMtYjU1Yy02Njg2YjAyNjk0MmIiLCJFbWFpbCI6IjFAMS5jb20iLCJleHAiOjE1NDQxODgwMDcsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjM5MzkvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo2MzkzOS8ifQ.GTFmUKiAfLTaOuv7rZ-g4Cns033RWehB8u3iFB59rFM"} /// </returns> [HttpPost] [Route("Login")] public async Task<IActionResult> Login([FromBody]UserLoginInput inputUser) { //拿到用戶名和密碼,用asp.net Core 自帶的Identity來進行登錄 var result = await _signInManager.PasswordSignInAsync(inputUser.UserName, inputUser.Password, inputUser.RememberMe, lockoutOnFailure: true); if (result.Succeeded) { //把你自己的密碼進行對稱加密 var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); //將加密后的密碼用JWT指定算法進行加密,這個加密算法有很多,可以去JWT官網上看 var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); //拿到當前登錄用戶 TinyBlog2User user = await _userManager.FindByEmailAsync(inputUser.Email); //獲取當前用戶的Claims IList<Claim> claimsList = await _userManager.GetClaimsAsync(user); //用各種信息組成一個JWT var unSecruityToken = new JwtSecurityToken(_config["Jwt:Issuer"], _config["Jwt:Issuer"], claimsList, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); //把JWT加密一下返回給客戶端 var token = new JwtSecurityTokenHandler().WriteToken(unSecruityToken); return Ok(new { token = token }); } else { return Unauthorized(); } } [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; } } }

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace TinyBlog2 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = Configuration["Jwt:Issuer"], ValidAudience = Configuration["Jwt:Issuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) }; }); services.AddAuthorization(options => { options.AddPolicy("VipOnly", policy => policy.RequireClaim("Role", "VipUser")); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseStaticFiles(); app.UseAuthentication(); app.UseHttpsRedirection(); app.UseMvc(); } } }
現在用戶登錄以后就會得到一串JWT。以后每次發請求的時候在頭部附帶JWT,瀏覽器就會認出用戶的身份,並且方便的做權限驗證了。這里附上PostMan設置。美滋滋。
7.試驗一波
8.搞定和額外說明
- 用戶注冊默認是依靠UserName來注冊的。
其實用戶系統才是最大的門檻。至於帖子的增刪改查。可以用很簡單的一篇博客就能搞定了。祝你開心。