AntDesign Pro + .NET Core 实现基于JWT的登录认证功能
很多同学说AgileConfig的UI实在是太丑了。我想想也是的,本来这个项目是我自己使用的,一开始甚至连UI都没有,全靠手动在数据库里修改数据。后来加上了UI也是使用了老掉牙的bootstrap3做为基础样式。前台框架也是使用了angularjs,同样是老掉牙的东西。过年期间终于下决心翻新AgileConfig的前端UI。最后选择的前端UI框架为AntDesignPro+React。至于为啥选Ant-DesignPro是因为他好看,而且流行,选择React是因为VUE跟Angular我都略知一二,干脆趁此机会学一学React为何物,为何这么流行。
登录的认证方案为JWT,其实本人对JWT不太感冒(请看这里《我们真的需要jwt吗?》),无奈大家都喜欢,那我也只能随大流。
其实基于ant-designpro的界面我已经翻的差不多了,因为它支持mock数据,所以我一行后台代码都没修改,已经把界面快写完了。从现在开始要真正的跟后端代码进行联调了。那么我们先从登录开始吧。先看看后端asp.netcore方面会如何进行修改。
修改ASP.NETCore后端代码
"JwtSetting":{ "SecurityKey":"xxxxxxxxxxxx",//密钥 "Issuer":"agileconfig.admin",//颁发者 "Audience":"agileconfig.admin",//接收者 "ExpireSeconds":20//过期时间s }
在appsettings.json文件添加jwt相关配置。
publicclassJwtSetting { staticJwtSetting() { Instance=newJwtSetting(); 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"]); } publicstringSecurityKey{get;set;} publicstringIssuer{get;set;} publicstringAudience{get;set;} publicintExpireSeconds{get;set;} publicstaticJwtSettingInstance { get; } }
定义一个JwtSetting类,用来读取配置。
publicvoidConfigureServices(IServiceCollectionservices) { services.AddMemoryCache(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options=> { options.TokenValidationParameters=newTokenValidationParameters { ValidIssuer=JwtSetting.Instance.Issuer, ValidAudience=JwtSetting.Instance.Audience, IssuerSigningKey=newSymmetricSecurityKey(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部署在不同的域名下,所以开启Cors。
//Thismethodgetscalledbytheruntime.UsethismethodtoconfiguretheHTTPrequestpipeline. publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv,IServiceProviderserviceProvider) { if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseMiddleware(); } app.UseCors(op=>{ op.AllowAnyOrigin(); op.AllowAnyMethod(); op.AllowAnyHeader(); }); app.UseWebSockets(newWebSocketOptions() { KeepAliveInterval=TimeSpan.FromSeconds(60), ReceiveBufferSize=2*1024 }); app.UseMiddleware (); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints=> { endpoints.MapDefaultControllerRoute(); }); }
修改Startup的Configure方法,配置Cors为Any。
publicclassJWT { publicstaticstringGetToken() { //创建用户身份标识,可按需要添加更多信息 varclaims=newClaim[] { newClaim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()), newClaim("id","admin",ClaimValueTypes.String),//用户id newClaim("name","admin"),//用户名 newClaim("admin",true.ToString(),ClaimValueTypes.Boolean)//是否是管理员 }; varkey=Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey); //创建令牌 vartoken=newJwtSecurityToken( issuer:JwtSetting.Instance.Issuer, audience:JwtSetting.Instance.Audience, signingCredentials:newSigningCredentials(newSymmetricSecurityKey(key),SecurityAlgorithms.HmacSha256Signature), claims:claims, notBefore:DateTime.Now, expires:DateTime.Now.AddSeconds(JwtSetting.Instance.ExpireSeconds) ); stringjwtToken=newJwtSecurityTokenHandler().WriteToken(token); returnjwtToken; } }
添加一个JWT静态类用来生成jwt的token。因为agileconfig的用户只有admin一个所以这里用户名,ID都直接写死。
[HttpPost("admin/jwt/login")] publicasyncTaskLogin4AntdPro([FromBody]LoginVMmodel) { stringpassword=model.password; if(string.IsNullOrEmpty(password)) { returnJson(new { status="error", message="密码不能为空" }); } varresult=await_settingService.ValidateAdminPassword(password); if(result) { varjwt=JWT.GetToken(); returnJson(new{ status="ok", token=jwt, type="Bearer", currentAuthority="admin" }); } returnJson(new { status="error", message="密码错误" }); }
新增一个Action方法做为登录的入口。在这里验证完密码后生成token,并且返回到前端。
到这里.netcore这边后端代码改动的差不多了。主要是添加jwt相关的东西,这些内容网上已经写了很多了,不在赘述。
下面开始修改前端代码。
修改AntDesignPro的代码
AntDesignPro已经为我们生成好了登录页面,登录的逻辑等,但是原来的登录是假的,也不支持jwttoken做为登录凭证,下面我们要修改多个文件来完善这个登录。
exportfunctionsetToken(token:string):void{ localStorage.setItem('token',token); } exportfunctiongetToken():string{ vartk=localStorage.getItem('token'); if(tk){ returntkasstring; } return''; }
在utils/authority.ts文件内新增2个方法,用来存储跟获取token。我们的jwttoken存储在localStorage里。
/**配置request请求时的默认参数*/ constrequest=extend({ prefix:'http://localhost:5000', errorHandler,//默认错误处理 credentials:'same-origin',//默认请求是否带上cookie, }); constauthHeaderInterceptor=(url:string,options:RequestOptionsInit)=>{ constauthHeader={Authorization:'Bearer'+getToken()}; return{ url:`${url}`, options:{...options,interceptors:true,headers:authHeader}, }; }; request.interceptors.request.use(authHeaderInterceptor);
修改utils/request.ts文件,定义一个添加Authorization头部的拦截器,并且使用这个拦截器,这有每次请求的时候自动会带上这个头部,把jwttoken传送到后台。
设置prefix为http://localhost:5000这是我们的后端api的服务地址,真正生产的时候会替换为正式地址。
设置credentials为same-origin。
exportasyncfunctionaccountLogin(params:LoginParamsType){ returnrequest('/admin/jwt/login',{ method:'POST', data:params, }); }
在services/login.ts文件内新增发起登录请求的方法。
effects:{ *login({payload},{call,put}){ constresponse=yieldcall(accountLogin,payload); yieldput({ type:'changeLoginStatus', payload:response, }); //Loginsuccessfully if(response.status==='ok'){ consturlParams=newURL(window.location.href); constparams=getPageQuery(); message.success('