Blazor client-side + webapi (.net core 3.1) 添加jwt驗證流程(非host)第二步 添加Identity


 

添加Identity數據上下文

安裝nuget包:Microsoft.AspNetCore.Identity.EntityFrameworkCore

 

創建ApplicationDbContext類

創建一個Data文件夾,並創建一個新類,添加以下內容

    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions options) : base(options)
        {
        }
    }

這個是一個默認Identity上下文,繼承於DbContext,包含了一些默認的表(Dbset)包括用戶,權限身份等等。

 

注冊數據庫和數據上下文

添加以下代碼到ConfigureServices中

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddIdentity<IdentityUser,IdentityRole>()
          .AddEntityFrameworkStores<ApplicationDbContext>();

 

在.net core 3.1中,AddDefaultIdentity() 被刪除了,所以這里用了AddIdentity()。

 

如果你需要使用SqlServer或者localdb的話,就安裝nuget:Microsoft.EntityFrameworkCore.SqlServer ,我自己是使用的Mysql,使用的nuget包是:Pomelo.EntityFrameworkCore.MySql。

這兩個包都會自動安裝EFCore包。

 

添加鏈接字符串到appsettings.json

localdb

  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AuthApiAndBlazor;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

 

 

Mysql

  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;database=AuthApiAndBlazor;uid=root;pwd=password;charset=utf8;sslMode=None",
  },

 

 

打開包管理器(或.Net CLI),添加遷移。

 

使用包管理員

PM> Add-Migration Init

 

使用命令行

C:\Users\Administrator\source\repos\AuthApiAndBlazor\server> dotnet ef migrations add init

 

當然,在3.1.2中,你可能會遇到錯誤,需要安裝 Microsoft.EntityFrameworkCore.Design包

Your startup project 'server' doesn't reference Microsoft.EntityFrameworkCore.Design. This package is required for the Entity Framework Core Tools to work. Ensure your startup project is correct, install the package, and try again.

 

檢查遷移文件,無誤后更新到服務器

 

包管理器

PM> Update-Database

 

命令行

C:\Users\Administrator\source\repos\AuthApiAndBlazor\server> dotnet ef database update

 

 

添加用戶驗證到管道(Configure method)中

            app.UseAuthorization(); //驗證用戶可以做什么
            app.UseAuthentication(); //用戶身份的驗證

 

為你的api 控制器添加授權驗證(用戶驗證)來測試我們的用戶成功了

    [Authorize] //添加特性
    public class WeatherForecastController : ControllerBase

 

生成后運行,這時候再訪問WeatherForecast的api就會跳轉到https://localhost:5002/Account/Login?ReturnUrl=%2FWeatherForecast Login操作的頁面上,這是Identity的預設鏈接,也是MVC標識(Identity)基架的默認頁面,在MVC下這會被自動創建,但是由於我們是手動添加到WebApi中的,所以需要更改,我們也只需要它返回401狀態碼即可,實際登陸應該由前端進行操作。

 

設置JWT

首先需要安裝Nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

 

添加代碼到ConfigureServices method中

 

            services.AddAuthentication((opts =>
            {
                opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;  //如果不加這行,將會跳轉到/Account/Login,也就是說你可以用視圖來處理未登錄
            })) //設置驗證方式為常量Bearer,即JWTBearer
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true, //驗證發行方
                        ValidateAudience = true, //驗證聽眾api
                        ValidateLifetime = true, //驗證生存時間
                        ValidateIssuerSigningKey = true, //驗證發行方密鑰
                        ValidIssuer = Configuration["JwtIssuer"], //發行方
                        ValidAudience = Configuration["JwtAudience"], //聽眾
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"])) //對稱密鑰
                    };
                });

 

 

這里的寫法是將JwtIssuer 、 JwtAudience 和 JwtSecurityKey 都放到appsettings.json文件中,你可以自己選擇,我沒有試過AddJwtBearer是單例還是怎樣,即它可能不是實時可修改的。

 

它在appsettings.json的樣子:

  "JwtSecurityKey": "RANDOM_KEY_MUST_NOT_BE_SHARED", //密鑰
  "JwtIssuer": "https://localhost:5002", //server的域名
  "JwtAudience": "http://localhost:5001", //client (Blazor的域名)
  "JwtExpiryInDays": 1 //過期時間

 

添加Register和Login控制器

在server中生成兩個api控制器,一個用於注冊,一個用來做登陸。

RegisterController:

    [Route("api/[controller]")]
    [ApiController]
    public class AccountsController : ControllerBase
    {
        private readonly UserManager<IdentityUser> _userManager;

        public AccountsController(UserManager<IdentityUser> userManager)
        {
            _userManager = userManager;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody]RegisterModel model)
        {
            //創建用戶,實際運用中我們一般會將IdentityUser更換為我們自定義的用戶
            var newUser = new IdentityUser { UserName = model.Email, Email = model.Email };

            var result = await _userManager.CreateAsync(newUser, model.Password);

            if (!result.Succeeded)
            {
                var errors = result.Errors.Select(x => x.Description);
                //提示失敗
                return Ok(new RegisterResult { Successful = false, Errors = errors });
            }
            //提示成功,讓前端做跳轉
            return Ok(new RegisterResult { Successful = true });
        }
    }

 

所需模型: (tips:這里因為我們前后端都是使用.net core 去實現,所以實際上我們可以將這些前后端都需要的模型,做成一個共享類庫,即保證了同步性,又節省了代碼量,如果你不這么做的話,你接下來還需要再添加一次這些模型到client項目)

RegisterModel(用於接收注冊前端參數)

 

    public class RegisterModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }

 

RegisterResult (用於回傳注冊狀態)

    public class RegisterResult
    {
        public bool Successful { get; set; }
        public IEnumerable<string> Errors { get; set; }
    }

 

為了方便,我們這里一次性也把登陸的模型都添加進去

 

LoginModel

    public class LoginModel
    {
        [Required]
        public string Email { get; set; }

        [Required]
        public string Password { get; set; }

        public bool RememberMe { get; set; }
    }

 

LoginResult

    public class LoginResult
    {
        public bool Successful { get; set; }
        public string Error { get; set; }
        public string Token { get; set; } //最終token
    }

UserModel

    public class UserModel
    {
        public string Email { get; set; }
        public bool IsAuthenticated { get; set; }
    }

 

LoginController的代碼:

    [Route("api/[controller]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        private readonly SignInManager<IdentityUser> _signInManager;

        public LoginController(IConfiguration configuration,
                               SignInManager<IdentityUser> signInManager)
        {
            _configuration = configuration;
            _signInManager = signInManager;
        }

        //api/login (GET)
        [HttpPost]
        public async Task<IActionResult> Login([FromBody] LoginModel login)
        {
            //嘗試登陸
            var result = await _signInManager.PasswordSignInAsync(login.Email, login.Password, false, false);

            if (!result.Succeeded) return BadRequest(new LoginResult { Successful = false, Error = "Username and password are invalid." }); //如果登陸失敗


            //payload
            var claims = new[]
            {
                new Claim(ClaimTypes.Name, login.Email)
            };

            //對稱密鑰
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]));
            //密鑰做簽名
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //過期時間
            var expiry = DateTime.Now.AddDays(Convert.ToInt32(_configuration["JwtExpiryInDays"]));

            //生成token
            var token = new JwtSecurityToken(
                _configuration["JwtIssuer"],
                _configuration["JwtAudience"],
                claims,
                expires: expiry,
                signingCredentials: creds
            );

            return Ok(new LoginResult { Successful = true, Token = new JwtSecurityTokenHandler().WriteToken(token) });
        }
    }

 

下面我們可以自己找個方法驗證下,這里就省略。 如果正確的話,你的WeatherForecast控制器應該是401未授權的。

當然我在實際運行中試過並不返回401而是302跳轉到了/Account/Login這個默認的登陸頁面,由於我們將全部內容都交由前端處理,並不打算用mvc方式處理登陸注冊,所以我們必須返回401以供前端識別。

使用postman做測試,首先WeatherForecast控制器是401返回(GET)

 

其次來試驗/api/register 注冊控制器

首先設置Content-Type為application/json

 

接着把json字符串放入body,經過實驗,提交的json中對象名稱可以首字母小寫也可以首字母大寫,跟實際類名相同都可,但是Blazor提交的json是會自動將首字母變小寫,但是后面的其他的大寫字母都不會變。當在使用blazor對接其他后端的時候要小心,因為如果要更改這個已經做好的過程,稍微有點麻煩

接下來可以獲得Response

{
    "successful": false,
    "errors": [
        "User name 'jiangsimpler@gmail.com' is already taken."
    ]
}

 

 

到此本章就結束了,下一章將開始處理client前端的注冊登陸和登出以及查詢授權api數據

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM