【分分鐘內搭建一個帶用戶系統的博客程序(一)用戶系統】asp.net core的Identity真香,EF真香!

不用不知道,一用香到爆。html

老哥是個屌絲前端,但也想寫點web應用耍一耍。以前弄過了NodeJs,也弄過JAVA,最近因爲寫遊戲的緣由用C#,索性上手一波asp.net core。前端

這篇博客記錄的是,如何在分分鐘內搭建一個博客程序。包括:web

  1. 發博客
  2. 看博客
  3. 用戶註冊登陸
  4. 用戶權限設置。

其中用的就是微軟提供的EntityFrame和Identity類庫。簡直他媽爽出翔。算法

1.快速生成一個項目

反正增刪改查就那麼回事兒,快速生成一個項目模板纔是主要的。數據庫

我不想建立一個帶Razor頁面的項目。由於我只須要API。老夫但是個前端!後端

 

這個時候按F5運行網站,而後就能夠用Postman向 http://localhost:55536/api/values發送請求了。若是有過開發經驗的人一眼就能看明白這是怎麼回事兒。api

 2.給程序添加用戶系統。

添加用戶系統的意思是,容許用戶註冊和登陸。瀏覽器

若是我用NodeJs或者Java,我就要開始寫數據庫了,甚至設計數據表。但是微軟已經把好用的東西給準備好了,那就是:Identity類庫。這個類庫老JB好了。我只須要輕輕點幾下,一套完備的用戶系統就能生成到個人代碼上。app

 

 我如今來解釋一波我進行了什麼操做。框架

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" };
        }
    }
}
UserController.cs

 

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();
        }
    }
}
Startup.cs

如今用戶登陸之後就會獲得一串JWT。之後每次發請求的時候在頭部附帶JWT,瀏覽器就會認出用戶的身份,而且方便的作權限驗證了。這裏附上PostMan設置。美滋滋。

 

 

 7.試驗一波

 

 8.搞定和額外說明

  • 用戶註冊默認是依靠UserName來註冊的。

其實用戶系統纔是最大的門檻。至於帖子的增刪改查。能夠用很簡單的一篇博客就能搞定了。祝你開心。

相關文章
相關標籤/搜索