AntDesignPro + .net core实现基于jwt的登录认证

很多同学说AgileConfig的UI实在是太丑了。我想想也是的,本来这个项目是我自己使用的,一开始甚至连UI都没有,全靠手动在数据库里修改数据。后来加上了UI也是使用了老掉牙的bootstrap3做为基础样式。前台框架也是使用了angularjs,同样是老掉牙的东西。过年期间终于下决心翻新AgileConfig的前端UI。最后选择的前端UI框架为AntDesign Pro + React。至于为啥选Ant-Design Pro是因为他好看,而且流行,选择React是因为VUEAngular我都略知一二,干脆趁此机会学一学React为何物,为何这么流行。
登录的认证方案为JWT,其实本人对JWT不太感冒(请看这里《我们真的需要jwt吗?》),无奈大家都喜欢,那我也只能随大流。

其实基于ant-design pro的界面我已经翻的差不多了,因为它支持mock数据,所以我一行后台代码都没修改,已经把界面快些完了。从现在开始要真正的跟后端代码进行联调了。那么我们先从登录开始吧。先看看后端asp.net core方面会如何进行修改。

修改ASP.NET Core后端代码

  "JwtSetting": {
    "SecurityKey": "xxxxxxxxxxxx", // 密钥
    "Issuer": "agileconfig.admin", // 颁发者
    "Audience": "agileconfig.admin", // 接收者
    "ExpireSeconds": 20 // 过期时间 s
  }

appsettings.json文件添加jwt相关配置。

  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方法,修改认证SchemeJwtBearerDefaults.AuthenticationScheme,在AddJwtBearer方法内配置jwt相关配置信息。因为前后端分离项目所以有可能api跟ui部署在不同的域名下,所以开启Core

     // 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();
            });
        }

修改StartupConfigure方法,配置CorsAny

    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静态类用来生成jwttoken。因为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相关的东西,这些内容网上已经写了很多了,不在赘述。
下面开始修改前端代码。

修改AntDesign Pro的代码

AntDesign Pro已经为我们生成好了登录页面,登录的逻辑等,但是原来的登录是假的,也不支持jwt token做为登录凭证,下面我们要修改多个文件来完善这个登录。

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里。

/** 配置request请求时的默认参数 */
const request = extend({
  prefix: 'http://localhost:5000',
  errorHandler, // 默认错误处理
  credentials: 'same-origin', // 默认请求是否带上cookie,
  headers: {
    Authorization: 'Bearer '+getToken(),
  },
});

修改utils/request.ts文件,在extend方法内添加jwt认证的头部Authorization为我们的token
设置prefixhttp://localhost:5000这是我们的后端api的服务地址,真正生产的时候会替换为正式地址。
设置credentialssame-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文件,修改effectslogin方法,在内部替换原来的fakeAccountLoginaccountLogin。同时修改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文件,修改effectsfetchCurrent方法为直接返回response。本来fetchCurrent是会去后台拉当前用户信息的,因为agileconfig的用户就admin一个,所以我直接写死了。

让我们试一下登录吧:)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要安装 `Microsoft.AspNet.WebApi` 和 `Microsoft.Owin.Security.Jwt` NuGet 包。 接下来,我们需要在 `WebApiConfig.cs` 文件中配置 Web API 路由: ```csharp public static void Register(HttpConfiguration config) { // 配置路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 配置 JWT 认证 ConfigureJwtAuth(config); } ``` 然后,我们需要在 `Web.config` 文件中配置 JWT 令牌的密钥和有效期: ```xml <appSettings> <add key="jwtSecret" value="my_secret_key" /> <add key="jwtExpireDays" value="7" /> </appSettings> ``` 接下来,我们需要创建一个 `JwtAuthManager` 类来管理 JWT 认证: ```csharp using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; public class JwtAuthManager : IJwtAuthManager { private readonly string _jwtSecret; private readonly double _jwtExpireDays; public JwtAuthManager(string jwtSecret, double jwtExpireDays) { _jwtSecret = jwtSecret; _jwtExpireDays = jwtExpireDays; } public string GenerateToken(IEnumerable<Claim> claims) { var key = Encoding.ASCII.GetBytes(_jwtSecret); var jwtToken = new JwtSecurityToken( claims: claims, expires: DateTime.Now.AddDays(_jwtExpireDays), signingCredentials: new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) ); var token = new JwtSecurityTokenHandler().WriteToken(jwtToken); return token; } } ``` 然后,我们需要创建一个 `JwtAuthAttribute` 特性,用于在控制器或操作方法上应用 JWT 认证: ```csharp [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class JwtAuthAttribute : AuthorizeAttribute { public override void OnAuthorization(HttpActionContext actionContext) { try { var token = actionContext.Request.Headers.Authorization.Parameter; var jwtAuthManager = actionContext.ControllerContext.Configuration .DependencyResolver.GetService(typeof(IJwtAuthManager)) as IJwtAuthManager; var principal = jwtAuthManager.ValidateToken(token); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal; } catch (Exception) { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); return; } base.OnAuthorization(actionContext); } } ``` 最后,我们需要在 `ConfigureJwtAuth` 方法中注册依赖项并配置 JWT 认证: ```csharp private static void ConfigureJwtAuth(HttpConfiguration config) { var jwtSecret = ConfigurationManager.AppSettings["jwtSecret"]; var jwtExpireDays = double.Parse(ConfigurationManager.AppSettings["jwtExpireDays"]); var container = new UnityContainer(); container.RegisterType<IJwtAuthManager, JwtAuthManager>( new InjectionConstructor(jwtSecret, jwtExpireDays)); config.DependencyResolver = new UnityResolver(container); config.Filters.Add(new JwtAuthAttribute()); } ``` 现在,我们可以在控制器或操作方法上应用 `JwtAuth` 特性来启用 JWT 认证: ```csharp [RoutePrefix("api/products")] public class ProductsController : ApiController { [HttpGet] [Route("")] [JwtAuth] public IHttpActionResult Get() { // ... } [HttpGet] [Route("{id}")] [JwtAuth] public IHttpActionResult Get(int id) { // ... } [HttpPost] [Route("")] [JwtAuth] public IHttpActionResult Post([FromBody] Product product) { // ... } // ... } ``` 这样,我们就成功地基于 JWT 实现了 Token 签名认证

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值