前言
GitHub地址
https://github.com/yingpanwang/MyShop/tree/dev_jwt
此文對應分支 dev_jwt
此文目的
上一篇文章中,我們使用Abp vNext構建了一個可以執行的簡單的API,但是整個站點沒有一個途徑去對我們的API訪問有限制,導致API完全是裸露在外的,如果要執行正常的商業API是完全不可行的,所以接下來我們會通過使用JWT(Json Web Toekn)的方式實現對API的訪問資料限制。
JWT簡介
什麼是JWT
現在API一般是分散式且要求是無狀態的,所以傳統的Session無法使用,JWT其實類似於早期API基於Cookie自定義使用者認證的形式,只是JWT的設計更為緊湊和易於擴充套件開放,使用方式更加便利基於JWT的鑑權機制類似於http協議也是無狀態的,它不需要在服務端去保留使用者的認證資訊或者會話資訊。
JWT的組成
JWT (以下簡稱Token)的組成分為三部分 header,playload,signature 完整的Token長這樣
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid2FuZ3lpbmdwYW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9tb2JpbGVwaG9uZSI6IjEyMiIsImV4cCI6MTYwNDI4MzczMSwiaXNzIjoiTXlTaG9wSXNzdWVyIiwiYXVkIjoiTXlTaG9wQXVkaWVuY2UifQ.U-2bEniEz82ECibBzk6C5tuj2JAdqISpbs5VrpA8W9s
header
包含型別及演算法資訊
{
"alg": "HS256",
"typ": "JWT"
}
通過base64加密後得到了
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
playload
包含標準的公開的生命和私有的宣告,不建議定義敏感資訊,因為該資訊是可以被解密的
部分公開的宣告
- iss: jwt簽發者
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大於簽發時間
私有的宣告
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name : 名稱
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: 標識
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone: 電話
{
"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"
}
通過base64加密後得到了
eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid2FuZ3lpbmdwYW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9tb2JpbGVwaG9uZSI6IjEyMiIsImV4cCI6MTYwNDI4MzczMSwiaXNzIjoiTXlTaG9wSXNzdWVyIiwiYXVkIjoiTXlTaG9wQXVkaWVuY2UifQ
signature
signature 由 三部分資訊組成,分別為base64加密後的header,playload用"."連線起來,通過宣告的演算法加密使用 服務端secret 作為鹽(salt)
三部分通過“.”連線起三部分組成了最終的Token
程式碼實踐
新增使用者服務
1.Domain中定義使用者實體User
public class User:BaseGuidEntity
{
[Required]
public string Account { get; set; }
/// <summary>
/// 名稱
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 真實名稱
/// </summary>
public string RealName { get; set; }
/// <summary>
/// 年齡
/// </summary>
public int Age { get; set; }
/// <summary>
/// 手機號
/// </summary>
public string Tel { get; set; }
/// <summary>
/// 住址
/// </summary>
public string Address { get; set; }
/// <summary>
/// 密碼
/// </summary>
public string Password { get; set; }
/// <summary>
/// 使用者狀態
/// </summary>
public UserStatusEnum UserStatus { get; set; }
}
public enum UserStatusEnum
{
Registered,//已註冊
Incompleted, // 未完善資訊
Completed,//完善資訊
Locked, // 鎖定
Deleted // 刪除
}
2.EntityFrameworkCore 中新增Table
UserCreatingExtension.cs
public static class UserCreatingExtension
{
public static void ConfigureUserStore(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<User>(option =>
{
option.ToTable("User");
option.ConfigureByConvention();
});
}
}
MyShopDbContext.cs
[ConnectionStringName("Default")]
public class MyShopDbContext:AbpDbContext<MyShopDbContext>
{
public MyShopDbContext(DbContextOptions<MyShopDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ConfigureProductStore();
builder.ConfigureOrderStore();
builder.ConfigureOrderItemStore();
builder.ConfigureCategoryStore();
builder.ConfigureBasketAndItemsStore();
// 配置使用者表
builder.ConfigureUserStore();
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Basket> Basket { get; set; }
public DbSet<BasketItem> BasketItems { get; set; }
//新增使用者表
public DbSet<User> Users { get; set; }
}
3.遷移生成User表
首先新增使用者表定義
[ConnectionStringName("Default")]
public class DbMigrationsContext : AbpDbContext<DbMigrationsContext>
{
public DbMigrationsContext(DbContextOptions<DbMigrationsContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureProductStore();
builder.ConfigureOrderStore();
builder.ConfigureOrderItemStore();
builder.ConfigureCategoryStore();
builder.ConfigureBasketAndItemsStore();
// 使用者配置
builder.ConfigureUserStore();
}
}
然後開啟程式包管理控制檯切換為遷移專案MyShop.EntityFrameworkCore.DbMigration並輸入
-
Add-Migration "AddUser"
-
Update-Database
此時User表就已經生成並對應Migration檔案
4.Application中構建UserApplication
Api中新增配置AppSetting.json
"Jwt": {
"SecurityKey": "1111111111111111111111111111111",
"Issuer": "MyShopIssuer",
"Audience": "MyShopAudience",
"Expires": 30 // 過期分鐘
}
IUserApplicationService
namespace MyShop.Application.Contract.User
{
public interface IUserApplicationService
{
Task<BaseResult<TokenInfo>> Register(UserRegisterDto registerInfo, CancellationToken cancellationToken);
Task<BaseResult<TokenInfo>> Login(UserLoginDto loginInfo);
}
}
AutoMapper Profiles中新增對映
namespace MyShop.Application.AutoMapper.Profiles
{
public class MyShopApplicationProfile:Profile
{
public MyShopApplicationProfile()
{
CreateMap<Product, ProductItemDto>().ReverseMap();
CreateMap<Order, OrderInfoDto>().ReverseMap();
CreateMap<Basket, BasketDto>().ReverseMap();
CreateMap<BasketItem, BasketItemDto>().ReverseMap();
CreateMap<InsertBasketItemDto, BasketItem>().ReverseMap();
// 使用者註冊資訊對映
CreateMap<UserRegisterDto, User>()
.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
namespace MyShop.Application
{
/// <summary>
/// 使用者服務
/// </summary>
public class UserApplicationService : ApplicationService, IUserApplicationService
{
private readonly IConfiguration _configuration;
private readonly IRepository<User,Guid> _userRepository;
/// <summary>
/// 構造
/// </summary>
/// <param name="userRepository">使用者倉儲</param>
/// <param name="configuration">配置資訊</param>
public UserApplicationService(IRepository<User, Guid> userRepository, IConfiguration configuration)
{
_userRepository = userRepository;
_configuration = configuration;
}
/// <summary>
/// 登入
/// </summary>
/// <param name="loginInfo">登入資訊</param>
/// <returns></returns>
public async Task<BaseResult<TokenInfo>> Login(UserLoginDto loginInfo)
{
if (string.IsNullOrEmpty(loginInfo.Account) || string.IsNullOrEmpty(loginInfo.Password))
return BaseResult<TokenInfo>.Failed("使用者名稱密碼不能為空!");
var user = await Task.FromResult(_userRepository.FirstOrDefault(p => p.Account == loginInfo.Account));
if (user == null)
{
return BaseResult<TokenInfo>.Failed("使用者名稱密碼錯誤!");
}
string md5Pwd = EncryptHelper.MD5Encrypt(loginInfo.Password);
if (user.Password != md5Pwd)
{
return BaseResult<TokenInfo>.Failed("使用者名稱密碼錯誤!");
}
var claims = GetClaims(user);
var token = GenerateToken(claims);
return BaseResult<TokenInfo>.Success(token);
}
/// <summary>
/// 註冊
/// </summary>
/// <param name="registerInfo">註冊資訊</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns></returns>
public async Task<BaseResult<TokenInfo>> Register(UserRegisterDto registerInfo,CancellationToken cancellationToken)
{
var user = ObjectMapper.Map<UserRegisterDto, User>(registerInfo);
var registeredUser = await _userRepository.InsertAsync(user, true, cancellationToken);
var claims = GetClaims(user);
var token = GenerateToken(claims);
return BaseResult<TokenInfo>.Success(token);
}
#region Token生成
private IEnumerable<Claim> 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;
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="claims">宣告</param>
/// <returns></returns>
private TokenInfo GenerateToken(IEnumerable<Claim> claims)
{
// 金鑰
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecurityKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// 過期時間
int expires = string.IsNullOrEmpty(_configuration["Expires"]) ? 30 : Convert.ToInt32(_configuration["Expires"]);
//生成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
}
}
啟用認證並新增相關Swagger配置
在需要啟動認證的站點模組中新增以下程式碼(MyShopApiModule)
MyShopApiModule.cs
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
{
// 注意是依賴於AspNetCoreMvc 而不是 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;
// 配置jwt
ConfigureJwt(service);
// 配置跨域
ConfigureCors(service);
// 配置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();
}
// 跨域
app.UseCors("AllowAll");
// swagger
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyShopApi");
});
app.UseRouting();
//新增jwt驗證 注意:必須先新增認證再新增授權中介軟體,且必須新增在UseRouting 和UseEndpoints之間
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;// 開發環境為false
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,//是否驗證Issuer
ValidateAudience = true,//是否驗證Audience
ValidateLifetime = true,//是否驗證失效時間
ClockSkew = TimeSpan.FromSeconds(30), // 偏移時間,所以實際過期時間 = 給定過期時間+偏移時間
ValidateIssuerSigningKey = true,//是否驗證SecurityKey
ValidAudience = configuration["Jwt:Audience"],//Audience
ValidIssuer = configuration["Jwt:Issuer"],//Issuer,這兩項和前面簽發jwt的設定一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecurityKey"]))//拿到SecurityKey
};
// 事件
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
return Task.CompletedTask;
},
OnChallenge = context =>
{
// 驗證失敗
BaseResult<object> result = new BaseResult<object>(ResponseResultCode.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 新增請求認證
//Bearer 的scheme定義
var securityScheme = new OpenApiSecurityScheme()
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
//引數新增在頭部
In = ParameterLocation.Header,
//使用Authorize頭部
Type = SecuritySchemeType.Http,
//內容為以 bearer開頭
Scheme = "Bearer",
BearerFormat = "JWT"
};
//把所有方法配置為增加bearer頭部資訊
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
}
}
統一響應格式
基礎 BaseResult
定義全體響應型別父類,並提供基礎響應成功及響應失敗結果建立靜態函式
/// <summary>
/// 基礎響應資訊
/// </summary>
/// <typeparam name="T">響應資料型別</typeparam>
public class BaseResult<T> where T:class
{
/// <summary>
/// 響應碼
/// </summary>
public ResponseResultCode Code { get; set; }
/// <summary>
/// 響應訊息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 響應資料
/// </summary>
public virtual T Data { get; set; }
/// <summary>
/// 響應成功資訊
/// </summary>
/// <param name="data">響應資料</param>
/// <returns></returns>
public static BaseResult<T> Success(T data,string message = "請求成功") => new BaseResult<T>(ResponseResultCode.Success,message, data);
/// <summary>
/// 響應失敗資訊
/// </summary>
/// <param name="message">響應資訊</param>
/// <returns></returns>
public static BaseResult<T> Failed(string message = "請求失敗!")
=> new BaseResult<T> (ResponseResultCode.Failed,message,null);
/// <summary>
/// 響應異常資訊
/// </summary>
/// <param name="message">響應資訊</param>
/// <returns></returns>
public static BaseResult<T> Error(string message = "請求失敗!")
=> new BaseResult<T>(ResponseResultCode.Error, message, null);
/// <summary>
/// 構造響應資訊
/// </summary>
/// <param name="code">響應碼</param>
/// <param name="message">響應訊息</param>
/// <param name="data">響應資料</param>
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
}
列表 ListResult
派生自BaseResult並新增泛型為IEnumerable
/// <summary>
/// 列表響應
/// </summary>
/// <typeparam name="T"></typeparam>
public class ListResult<T> : BaseResult<IEnumerable<T>> where T : class
{
public ListResult(ResponseResultCode code, string message, IEnumerable<T> data)
: base(code, message, data)
{
}
}
分頁列表 PagedResult
派生自BaseResult並新增PageData分頁資料型別泛型
public class PagedResult<T> : BaseResult<PageData<T>>
{
public PagedResult(ResponseResultCode code, string message, PageData<T> data) : base(code, message, data)
{
}
/// <summary>
/// 響應成功資訊
/// </summary>
/// <param name="total">資料總條數</param>
/// <param name="list">分頁列表資訊</param>
/// <returns></returns>
public static PagedResult<T> Success(int total,IEnumerable<T> list,string message= "請求成功")
=> new PagedResult<T> (ResponseResultCode.Success,message,new PageData<T> (total,list));
}
/// <summary>
/// 分頁資料
/// </summary>
/// <typeparam name="T">資料型別</typeparam>
public class PageData<T>
{
/// <summary>
/// 構造
/// </summary>
/// <param name="total">資料總條數</param>
/// <param name="list">資料集合</param>
public PageData(int total,IEnumerable<T> list)
{
this.Total = total;
this.Data = list;
}
/// <summary>
/// 資料總條數
/// </summary>
public int Total { get; set; }
/// <summary>
/// 資料集合
/// </summary>
public IEnumerable<T> Data { get; set; }
}
自定義異常
在我們新增自定義異常時需要先將abp vNext 預設提供的全域性異常過濾器移除。
在Module的ConfigureServices中新增移除程式碼
// 移除Abp異常過濾器
Configure<MvcOptions>(options =>
{
var index = options.Filters.ToList().FindIndex(filter => filter is ServiceFilterAttribute attr && attr.ServiceType.Equals(typeof(AbpExceptionFilter)));
if (index > -1)
options.Filters.RemoveAt(index);
});
定義MyShop自定義異常中介軟體
/// <summary>
/// MyShop異常中介軟體
/// </summary>
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<object> result = null; ;
bool handle = true;
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
result = new BaseResult<object>(ResponseResultCode.Unauthorized, "未授權!", null);
}
else if (context.Response.StatusCode == (int)HttpStatusCode.InternalServerError)
{
result = new BaseResult<object>(ResponseResultCode.Error, "服務繁忙!", null);
}
else
{
handle = false;
}
if(handle) await context.Response.WriteAsync(JsonConvert.SerializeObject(result), Encoding.UTF8);
}
}
為了方便通過IApplicationBuilder呼叫這裡我們新增個擴充套件方法用於方便新增我們的自定義異常中介軟體
public static class MiddlewareExtensions
{
/// <summary>
/// MyShop異常中介軟體
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseMyShopExceptionMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<MyShopExceptionMiddleware>();
return app;
}
}
最後在 Module類中 新增對應的中介軟體
app.UseMyShopExceptionMiddleware();
程式執行
直接訪問Order列表
由於我們的Order服務帶有[Authrorize]特性
所以結果直接顯示為 未授權
訪問登入介面獲取token
通過註冊介面註冊一個使用者用於登入
使用wyp登入
使用token訪問Order列表
將獲取到的token新增到 請求頭中的Authoritarian中 格式為:Bearer {token},這裡我們通過swagger 配置了所以直接可以使用它的小綠鎖
隨後訪問之前的Order介面
顯示請求成功
手動丟擲異常顯示自定義異常響應
訪問一個未實現的介面服務
正常顯示了我們的全域性異常資訊