不少同窗說AgileConfig的UI實在是太醜了。我想一想也是的,原本這個項目是我本身使用的,一開始甚至連UI都沒有,全靠手動在數據庫裏修改數據。後來加上了UI也是使用了老掉牙的bootstrap3作爲基礎樣式。前臺框架也是使用了angularjs,一樣是老掉牙的東西。過年期間終於下決心翻新AgileConfig的前端UI。最後選擇的前端UI框架爲AntDesign Pro + React。至於爲啥選Ant-Design Pro是由於他好看,並且流行,選擇React是由於VUE跟Angular我都略知一二,乾脆趁此機會學一學React爲什麼物,爲什麼這麼流行。
登陸的認證方案爲JWT,其實本人對JWT不太感冒(請看這裏《咱們真的須要jwt嗎?》),無奈你們都喜歡,那我也只能隨大流。
其實基於ant-design pro的界面我已經翻的差很少了,由於它支持mock數據,因此我一行後臺代碼都沒修改,已經把界面快些完了。從如今開始要真正的跟後端代碼進行聯調了。那麼咱們先從登陸開始吧。先看看後端asp.net core方面會如何進行修改。前端
"JwtSetting": { "SecurityKey": "xxxxxxxxxxxx", // 密鑰 "Issuer": "agileconfig.admin", // 頒發者 "Audience": "agileconfig.admin", // 接收者 "ExpireSeconds": 20 // 過時時間 s }
在appsettings.json文件添加jwt相關配置。angularjs
public class JwtSetting { static JwtSetting() { Instance = new JwtSetting(); Instance.Audience = Global.Config["JwtSetting:Audience"]; Instance.SecurityKey = Global.Config["JwtSetting:SecurityKey"]; Instance.Issuer = Global.Config["JwtSetting:Issuer"]; Instance.ExpireSeconds = int.Parse(Global.Config["JwtSetting:ExpireSeconds"]); } public string SecurityKey { get; set; } public string Issuer { get; set; } public string Audience { get; set; } public int ExpireSeconds { get; set; } public static JwtSetting Instance { get; } }
定義一個JwtSetting類,用來讀取配置。數據庫
public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = JwtSetting.Instance.Issuer, ValidAudience = JwtSetting.Instance.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey)), }; }); services.AddCors(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddRazorRuntimeCompilation(); services.AddFreeSqlDbContext(); services.AddBusinessServices(); services.AddAntiforgery(o => o.SuppressXFrameOptionsHeader = true); }
修改Startup文件的ConfigureServices方法,修改認證Scheme爲JwtBearerDefaults.AuthenticationScheme,在AddJwtBearer方法內配置jwt相關配置信息。由於先後端分離項目因此有可能api跟ui部署在不一樣的域名下,因此開啓Core。json
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseMiddleware<ExceptionHandlerMiddleware>(); } app.UseCors(op=> { op.AllowAnyOrigin(); op.AllowAnyMethod(); op.AllowAnyHeader(); }); app.UseWebSockets(new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(60), ReceiveBufferSize = 2 * 1024 }); app.UseMiddleware<WebsocketHandlerMiddleware>(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); }
修改Startup的Configure方法,配置Cors爲Any。bootstrap
public class JWT { public static string GetToken() { //建立用戶身份標識,可按須要添加更多信息 var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim("id", "admin", ClaimValueTypes.String), // 用戶id new Claim("name", "admin"), // 用戶名 new Claim("admin", true.ToString() ,ClaimValueTypes.Boolean) // 是不是管理員 }; var key = Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey); //建立令牌 var token = new JwtSecurityToken( issuer: JwtSetting.Instance.Issuer, audience: JwtSetting.Instance.Audience, signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature), claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddSeconds(JwtSetting.Instance.ExpireSeconds) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; } }
添加一個JWT靜態類用來生成jwt的token。由於agileconfig的用戶只有admin一個因此這裏用戶名,ID都直接寫死。後端
[HttpPost("admin/jwt/login")] public async Task<IActionResult> Login4AntdPro([FromBody] LoginVM model) { string password = model.password; if (string.IsNullOrEmpty(password)) { return Json(new { status = "error", message = "密碼不能爲空" }); } var result = await _settingService.ValidateAdminPassword(password); if (result) { var jwt = JWT.GetToken(); return Json(new { status="ok", token=jwt, type= "Bearer", currentAuthority = "admin" }); } return Json(new { status = "error", message = "密碼錯誤" }); }
新增一個Action方法作爲登陸的入口。在這裏驗證完密碼後生成token,而且返回到前端。
到這裏.net core這邊後端代碼改動的差很少了。主要是添加jwt相關的東西,這些內容網上已經寫了不少了,不在贅述。
下面開始修改前端代碼。api
AntDesign Pro已經爲咱們生成好了登陸頁面,登陸的邏輯等,可是原來的登陸是假的,也不支持jwt token作爲登陸憑證,下面咱們要修改多個文件來完善這個登陸。cookie
export function setToken(token:string): void { localStorage.setItem('token', token); } export function getToken(): string { var tk = localStorage.getItem('token'); if (tk) { return tk as string; } return ''; }
在utils/authority.ts文件內新增2個方法,用來存儲跟獲取token。咱們的jwt token存儲在localStorage裏。app
/** 配置request請求時的默認參數 */ const request = extend({ prefix: 'http://localhost:5000', errorHandler, // 默認錯誤處理 credentials: 'same-origin', // 默認請求是否帶上cookie, headers: { Authorization: 'Bearer '+getToken(), }, });
修改utils/request.ts文件,在extend方法內添加jwt認證的頭部Authorization爲咱們的token。
設置prefix爲http://localhost:5000這是咱們的後端api的服務地址,真正生產的時候會替換爲正式地址。
設置credentials爲same-origin。框架
export async function accountLogin(params: LoginParamsType) { return request('/admin/jwt/login', { method: 'POST', data: params, }); }
在services/login.ts文件內新增發起登陸請求的方法。
effects: { *login({ payload }, { call, put }) { const response = yield call(accountLogin, payload); yield put({ type: 'changeLoginStatus', payload: response, }); // Login successfully if (response.status === 'ok') { const urlParams = new URL(window.location.href); const params = getPageQuery(); message.success('???? ???? ???? 登陸成功!'); let { redirect } = params as { redirect: string }; if (redirect) { console.log('redirect url ' , redirect); const redirectUrlParams = new URL(redirect); if (redirectUrlParams.origin === urlParams.origin) { redirect = redirect.substr(urlParams.origin.length); if (redirect.match(/^\/.*#/)) { redirect = redirect.substr(redirect.indexOf('#') + 1); } } else { window.location.href = '/'; return; } } history.replace(redirect || '/'); } }, reducers: { changeLoginStatus(state, { payload }) { setAuthority(payload.currentAuthority); setToken(payload.token) return { ...state, status: payload.status, type: payload.type, }; }, },
修改models/login.ts文件,修改effects的login方法,在內部替換原來的fakeAccountLogin爲accountLogin。同時修改reducers內部的changeLoginStatus方法,添加setToken的代碼,這有修改後登陸成功後token就會被存儲起來。
effects: { *fetch(_, { call, put }) { const response = yield call(queryUsers); yield put({ type: 'save', payload: response, }); }, *fetchCurrent(_, { call, put }) { const response = { name: '管理員', userid: 'admin' }; yield put({ type: 'saveCurrentUser', payload: response, }); }, },
修改models/user.ts文件,修改effects的fetchCurrent方法爲直接返回response。原本fetchCurrent是會去後臺拉當前用戶信息的,由於agileconfig的用戶就admin一個,因此我直接寫死了。