当前位置:网站首页>Try to build my mall from scratch (2): use JWT to protect our information security and perfect swagger configuration

Try to build my mall from scratch (2): use JWT to protect our information security and perfect swagger configuration

2020-11-06 20:13:45 itread01

## 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 result = new BaseResult(ResponseResultCode.Unauthorized," Unauthorized ",null); context.HandleResponse(); context.Response.ContentType = "application/json;utf-8"; context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync(JsonConvert.SerializeObject(result), Encoding.UTF8); }, OnForbidden = context => { return Task.CompletedTask; }, OnMessageReceived = context => { return Task.CompletedTask; } }; }); } private void ConfigureCors(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod(); }); }); } private void ConfigureSwagger(IServiceCollection services) { services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo() { Title = "MyShopApi", Version = "v0.1" }); options.DocInclusionPredicate((docName, predicate) => true); options.CustomSchemaIds(type => type.FullName); var basePath = PlatformServices.Default.Application.ApplicationBasePath; options.IncludeXmlComments(Path.Combine(basePath, "MyShop.Application.xml")); options.IncludeXmlComments(Path.Combine(basePath, "MyShop.Application.Contract.xml")); #region New request authentication //Bearer Of scheme Define var securityScheme = new OpenApiSecurityScheme() { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", // The argument is added to the head In = ParameterLocation.Header, // Use Authorize Head Type = SecuritySchemeType.Http, // The content is based on bearer The beginning Scheme = "Bearer", BearerFormat = "JWT" }; // Configure all methods to add bearer Head information var securityRequirement = new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "MyShopApi" } }, new string[] {} } }; options.AddSecurityDefinition("MyShopApi", securityScheme); options.AddSecurityRequirement(securityRequirement); #endregion }); } #endregion } } ``` ### Unified response format ###### Foundation BaseResult Define all response type parents , It also provides the results of basic response success and response failure, and establishes static functions ``` csharp /// /// Basic response information /// /// Response data type public class BaseResult where T:class { /// /// Response code /// public ResponseResultCode Code { get; set; } /// /// Response message /// public string Message { get; set; } /// /// Response data /// public virtual T Data { get; set; } /// /// Respond to success information /// /// Response data /// public static BaseResult Success(T data,string message = " Request succeeded ") => new BaseResult (ResponseResultCode.Success,message, data); /// /// Response failure information /// /// Respond to information /// public static BaseResult Failed(string message = " Request failed !") => new BaseResult (ResponseResultCode.Failed,message,null); /// /// Response to exception information /// /// Respond to information /// public static BaseResult Error(string message = " Request failed !") => new BaseResult (ResponseResultCode.Error, message, null); /// /// Construct response information /// /// Response code /// Response message /// Response data public BaseResult(ResponseResultCode code,string message,T data) { this.Code = code; this.Message = message; this.Data = data; } } public enum ResponseResultCode { Success = 200, Failed = 400, Unauthorized = 401, Error = 500 } ``` ###### list ListResult Derive from BaseResult And add the generic type as IEnumerable ``` csharp /// /// List response /// /// public class ListResult : BaseResult > where T : class { public ListResult(ResponseResultCode code, string message, IEnumerable data) : base(code, message, data) { } } ``` ###### Paging list PagedResult Derive from BaseResult And new PageData Paging data type generics ``` csharp public class PagedResult : BaseResult > { public PagedResult(ResponseResultCode code, string message, PageData data) : base(code, message, data) { } /// /// Respond to success information /// /// Total number of data /// Pagination list information /// public static PagedResult Success(int total,IEnumerable list,string message= " Request succeeded ") => new PagedResult (ResponseResultCode.Success,message,new PageData (total,list)); } /// /// Paging data /// /// Data type public class PageData { /// /// Structure /// /// Total number of data /// Data collection public PageData(int total,IEnumerable list) { this.Total = total; this.Data = list; } /// /// Total number of data /// public int Total { get; set; } /// /// Data collection /// public IEnumerable Data { get; set; } } ``` ### Custom exception When we add a custom exception, we need to add abp vNext The default global exception filter is removed . stay Module Of ConfigureServices Add remove code ``` csharp // remove Abp Abnormal filter Configure (options => { var index = options.Filters.ToList().FindIndex(filter => filter is ServiceFilterAttribute attr && attr.ServiceType.Equals(typeof(AbpExceptionFilter))); if (index > -1) options.Filters.RemoveAt(index); }); ``` Define MyShop Custom exception mediation Software ``` csharp /// /// MyShop Exception mediation Software /// public class MyShopExceptionMiddleware { private readonly RequestDelegate _next; public MyShopExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { await HandleException(context, ex); } finally { await HandleException(context); } } private async Task HandleException(HttpContext context, Exception ex = null) { BaseResult result = null; ; bool handle = true; if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) { result = new BaseResult(ResponseResultCode.Unauthorized, " Unauthorized !", null); } else if (context.Response.StatusCode == (int)HttpStatusCode.InternalServerError) { result = new BaseResult(ResponseResultCode.Error, " Service is busy !", null); } else { handle = false; } if(handle) await context.Response.WriteAsync(JsonConvert.SerializeObject(result), Encoding.UTF8); } } ``` In order to facilitate the passage of **IApplicationBuilder** Call here to add an extension kit method to add our custom exception mediation software ``` csharp public static class MiddlewareExtensions { /// /// MyShop Exception mediation Software /// /// /// public static IApplicationBuilder UseMyShopExceptionMiddleware(this IApplicationBuilder app) { app.UseMiddleware (); return app; } } ``` Finally in Module Class Add the corresponding intermediary software ``` csharp app.UseMyShopExceptionMiddleware(); ``` ## Program execution ###### Direct access Order list Because of our Order The service comes with [Authrorize] characteristic ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106100634088-416112452.png) So it turns out to be Unauthorized ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106100748024-1781194612.png) ###### Access the login interface to get token Register a user through the registration interface for login Use wyp Log in ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106100918955-1170133007.png) ###### Use token Visit Order list Will get token Add to Ask for Authoritarian in The format is :Bearer {token}, Here we go through swagger Configured, so you can directly use its small green lock ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106101126855-715219311.png) ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106101151911-1666361778.png) And then, before we visit Order Interface Display request success ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106101240299-201331757.png) ###### Manual throw exception display custom exception response Accessing an unimplemented interface service ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106101407155-996849893.png) Normal display of our global exception information ![](https://img2020.cnblogs.com/blog/920403/202011/920403-20201106101445020-2360098

You may also like it …

1.042200088501

版权声明
本文为[itread01]所创,转载请带上原文链接,感谢