## Preface ### GitHub Address > https://github.com/yingpanwang/MyShop/tree/dev_jwt > This paper corresponds to the branch dev_jwt ### Purpose of this article In the last article , We use Abp vNext Build a simple executable API, But there's no way for the entire site to go to our API Access is limited , Lead to API Completely naked , If you want to do normal business API It's totally impossible , So next we'll use JWT(Json Web Toekn) The way to realize to API Access restrictions for . #### JWT Introduction ###### What is JWT Now API It is generally decentralized and requires stateless , So the traditional Session Can't use ,JWT It's similar to the early days API Based on Cookie Custom user authentication form , It's just JWT The design is more compact and easy to expand the package open , It is more convenient to use because JWT The authentication mechanism of is similar to http The agreement is also stateless , It doesn't need to keep the user's authentication information or session information in the server . ###### JWT Composition of JWT ( Hereinafter referred to as Token) Its composition is divided into three parts header,playload,signature complete Token Long like this eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid2FuZ3lpbmdwYW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9tb2JpbGVwaG9uZSI6IjEyMiIsImV4cCI6MTYwNDI4MzczMSwiaXNzIjoiTXlTaG9wSXNzdWVyIiwiYXVkIjoiTXlTaG9wQXVkaWVuY2UifQ.U-2bEniEz82ECibBzk6C5tuj2JAdqISpbs5VrpA8W9s **header** Contains type and algorithm information ``` JSON { "alg": "HS256", "typ": "JWT" } ``` Through base64 After encryption, you get eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 **playload** Contains standard public life and private announcements , It is not recommended to define sensitive information , Because the information can be decrypted Part of the public announcement * iss: jwt Issued by * aud: receive jwt On the side of * exp: jwt The expiration date of , This expiration time must be greater than the issuing time Private announcement * http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name : Name * http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: Identification * http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone: Telephone ``` JSON { "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "wangyingpan", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "2", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone": "122", "exp": 1604283731, "iss": "MyShopIssuer", "aud": "MyShopAudience" } ``` Through base64 After encryption, you get eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid2FuZ3lpbmdwYW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9tb2JpbGVwaG9uZSI6IjEyMiIsImV4cCI6MTYwNDI4MzczMSwiaXNzIjoiTXlTaG9wSXNzdWVyIiwiYXVkIjoiTXlTaG9wQXVkaWVuY2UifQ **signature** signature from Three parts of information , They are base64 Encrypted **header**,**playload** use "." Connect , Encrypted by declarative calculus Server-side **secret** As salt (**salt**) Three parts through “.” Connecting three parts makes up the final Token ## Code practice ### New user services ##### 1.Domain User entities are defined in User ``` csharp public class User:BaseGuidEntity { [Required] public string Account { get; set; } ///
/// Name /// public string NickName { get; set; } ///
/// The real name /// public string RealName { get; set; } ///
/// Age /// public int Age { get; set; } ///
/// Cell phone number /// public string Tel { get; set; } ///
/// address /// public string Address { get; set; } ///
/// code /// public string Password { get; set; } ///
/// User status /// public UserStatusEnum UserStatus { get; set; } } public enum UserStatusEnum { Registered,// Registered Incompleted, // Incomplete information Completed,// Perfect information Locked, // Lock Deleted // Delete } ``` ##### 2.EntityFrameworkCore Newly added Table **UserCreatingExtension.cs** ``` csharp public static class UserCreatingExtension { public static void ConfigureUserStore(this ModelBuilder builder) { Check.NotNull(builder, nameof(builder)); builder.Entity
(option => { option.ToTable("User"); option.ConfigureByConvention(); }); } } ``` **MyShopDbContext.cs** ``` csharp [ConnectionStringName("Default")] public class MyShopDbContext:AbpDbContext
{ public MyShopDbContext(DbContextOptions
options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { builder.ConfigureProductStore(); builder.ConfigureOrderStore(); builder.ConfigureOrderItemStore(); builder.ConfigureCategoryStore(); builder.ConfigureBasketAndItemsStore(); // Configure user table builder.ConfigureUserStore(); } public DbSet
Products { get; set; } public DbSet
Orders { get; set; } public DbSet
OrderItems { get; set; } public DbSet
Categories { get; set; } public DbSet
Basket { get; set; } public DbSet
BasketItems { get; set; } // Add user table public DbSet
Users { get; set; } } ``` ##### 3. Migration generates User surface First add a user table definition ``` csharp [ConnectionStringName("Default")] public class DbMigrationsContext : AbpDbContext
{ public DbMigrationsContext(DbContextOptions
options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ConfigureProductStore(); builder.ConfigureOrderStore(); builder.ConfigureOrderItemStore(); builder.ConfigureCategoryStore(); builder.ConfigureBasketAndItemsStore(); // User configuration builder.ConfigureUserStore(); } } ``` Then open the package management console and switch to the migration project **MyShop.EntityFrameworkCore.DbMigration** And type * >Add-Migration "AddUser" * >Update-Database Now User The table is already generated and corresponds to Migration Archives ##### 4.Application Build UserApplication ###### Api New configuration in AppSetting.json ``` csharp "Jwt": { "SecurityKey": "1111111111111111111111111111111", "Issuer": "MyShopIssuer", "Audience": "MyShopAudience", "Expires": 30 // Expired minutes } ``` ###### IUserApplicationService ``` csharp namespace MyShop.Application.Contract.User { public interface IUserApplicationService { Task
> Register(UserRegisterDto registerInfo, CancellationToken cancellationToken); Task
> Login(UserLoginDto loginInfo); } } ``` ###### AutoMapper Profiles A new pair of images has been added in ``` csharp namespace MyShop.Application.AutoMapper.Profiles { public class MyShopApplicationProfile:Profile { public MyShopApplicationProfile() { CreateMap
().ReverseMap(); CreateMap
().ReverseMap(); CreateMap
().ReverseMap(); CreateMap
().ReverseMap(); CreateMap
().ReverseMap(); // User registration information mapping CreateMap
() .ForMember(src=>src.UserStatus ,opt=>opt.MapFrom(src=> UserStatusEnum.Registered)) .ForMember(src=>src.Password , opt=>opt.MapFrom(src=> EncryptHelper.MD5Encrypt(src.Password,string.Empty))); } } } ``` ###### UserApplicationService ``` csharp namespace MyShop.Application { ///
/// User services /// public class UserApplicationService : ApplicationService, IUserApplicationService { private readonly IConfiguration _configuration; private readonly IRepository
_userRepository; ///
/// Structure /// ///
User storage ///
Configuration information public UserApplicationService(IRepository
userRepository, IConfiguration configuration) { _userRepository = userRepository; _configuration = configuration; } ///
/// Log in /// ///
Login information ///
public async Task
> Login(UserLoginDto loginInfo) { if (string.IsNullOrEmpty(loginInfo.Account) || string.IsNullOrEmpty(loginInfo.Password)) return BaseResult
.Failed(" User name password cannot be empty !"); var user = await Task.FromResult(_userRepository.FirstOrDefault(p => p.Account == loginInfo.Account)); if (user == null) { return BaseResult
.Failed(" User name password error !"); } string md5Pwd = EncryptHelper.MD5Encrypt(loginInfo.Password); if (user.Password != md5Pwd) { return BaseResult
.Failed(" User name password error !"); } var claims = GetClaims(user); var token = GenerateToken(claims); return BaseResult
.Success(token); } ///
/// Register /// ///
Registration information ///
Cancel the token ///
public async Task
> Register(UserRegisterDto registerInfo,CancellationToken cancellationToken) { var user = ObjectMapper.Map
(registerInfo); var registeredUser = await _userRepository.InsertAsync(user, true, cancellationToken); var claims = GetClaims(user); var token = GenerateToken(claims); return BaseResult
.Success(token); } #region Token Generate private IEnumerable
GetClaims(User user) { var claims = new[] { new Claim(AbpClaimTypes.UserName,user.NickName), new Claim(AbpClaimTypes.UserId,user.Id.ToString()), new Claim(AbpClaimTypes.PhoneNumber,user.Tel), new Claim(AbpClaimTypes.SurName, user.UserStatus == UserStatusEnum.Completed ?user.RealName:string.Empty) }; return claims; } ///
/// Generate token /// ///
Declare ///
private TokenInfo GenerateToken(IEnumerable
claims) { // Key var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecurityKey"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // Expiration time int expires = string.IsNullOrEmpty(_configuration["Expires"]) ? 30 : Convert.ToInt32(_configuration["Expires"]); // Generate token var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(expires), signingCredentials: creds); return new TokenInfo() { Expire = expires, Token = new JwtSecurityTokenHandler().WriteToken(token) }; } #endregion } } ``` ### Enable authentication and add related Swagger To configure Add the following code to the site module that needs to start Authentication (MyShopApiModule) ###### MyShopApiModule.cs ``` csharp using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using MyShop.Admin.Application; using MyShop.Admin.Application.Services; using MyShop.Api.Middleware; using MyShop.Application; using MyShop.Application.Contract.Order; using MyShop.Application.Contract.Product; using MyShop.EntityFrameworkCore; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.AspNetCore; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace MyShop.Api { // Attention depends on AspNetCoreMvc instead of AspNetCore [DependsOn(typeof(AbpAspNetCoreMvcModule),typeof(AbpAutofacModule))] [DependsOn(typeof(MyShopApplicationModule),typeof(MyShopEntityFrameworkCoreModule),typeof(MyShopAdminApplicationModule))] public class MyShopApiModule :AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { var service = context.Services; // To configure jwt ConfigureJwt(service); // Configure cross domain ConfigureCors(service); // To configure swagger ConfigureSwagger(service); service.Configure((AbpAspNetCoreMvcOptions options) => { options.ConventionalControllers.Create(typeof(Application.ProductApplicationService).Assembly); options.ConventionalControllers.Create(typeof(Application.OrderApplicationService).Assembly); options.ConventionalControllers.Create(typeof(Application.UserApplicationService).Assembly); options.ConventionalControllers.Create(typeof(Application.BasketApplicationService).Assembly); options.ConventionalControllers.Create(typeof(Admin.Application.Services.ProductApplicationService).Assembly, options => { options.RootPath = "admin"; }); }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var env = context.GetEnvironment(); var app = context.GetApplicationBuilder(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } // Cross domain app.UseCors("AllowAll"); // swagger app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyShopApi"); }); app.UseRouting(); // newly added jwt Verification Be careful : You must add authentication first, and then add authorized intermediary software , And must be added in UseRouting and UseEndpoints Between app.UseAuthentication(); app.UseAuthorization(); app.UseConfiguredEndpoints(); } #region ServicesConfigure private void ConfigureJwt(IServiceCollection services) { var configuration = services.GetConfiguration(); services .AddAuthentication(options=> { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options=> { options.RequireHttpsMetadata = false;// The development environment is false options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = true,// Whether to verify Issuer ValidateAudience = true,// Whether to verify Audience ValidateLifetime = true,// Whether to verify the expiration time ClockSkew = TimeSpan.FromSeconds(30), // Offset time , So the actual expiration time = Given the expiration time + Offset time ValidateIssuerSigningKey = true,// Whether to verify SecurityKey ValidAudience = configuration["Jwt:Audience"],//Audience ValidIssuer = configuration["Jwt:Issuer"],//Issuer, These two items and the previous issue jwt The settings are consistent IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecurityKey"]))// Get SecurityKey }; // event options.Events = new JwtBearerEvents() { OnAuthenticationFailed = context => { return Task.CompletedTask; }, OnChallenge = context => { // Validation failed BaseResult