把旧系统迁移到.Net Core 2.0 日记 (18) --JWT 认证(Json Web Token)

Posted GuGu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了把旧系统迁移到.Net Core 2.0 日记 (18) --JWT 认证(Json Web Token)相关的知识,希望对你有一定的参考价值。

我们最常用的认证系统是Cookie认证,通常用一般需要人工登录的系统,用户访问授权范围的url时,会自动Redirect到Account/Login,登录后把认证结果存在cookie里。

系统只要找到这个cookie就认为这个web用户是已经登录的了。

通常的代码段是这样的,StartUp.cs

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie();
public async Task<IActionResult> Login(IFormCollection form)
        {
            string userName = form["txtLoginId"];
            string pwd = form["txtPwd"];
            if (Validate(userName, pwd))
            {
                var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, userName) }, "Basic");
                var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
                return Json(new { isSuccess = true, message = "登录成功" });
            }



        }

如果我们用RestFul API写接口给前端或第三方程序使用时,这种重定向登录的方式就不合适了。

最合适的方法是,前端访问一个Login接口,获得一个AccessToken,然后用这个Token去访问后端的资源, 后端验证这个token的正确性,就放行让前端访问。

一开始比较笨的方法,是在每个接口方法都加一个accessToken的字段,这样不够优雅,可以把这个token放在Header里。这就是JWT认证

JWT有什么好处?

1、支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.

2、无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

4、更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascripthtml,图片等),而你的服务端只要提供API即可.

5、去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.

6、更适用于移动应用: 当你的客户端是一个原生平台(ios, android)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

7、CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。

8、性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.

9、不需要为登录页面做特殊处理

10、基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库和多家公司的支持.

JWT是用于验证而非加密,任何人即使没有密钥secret,header与payload中的数据都是可以获取的。
使用 jwt.io,可以进行JWT的编码、解码和验证

.net Core 使用Jwt认证的方法

1. 在startup.cs 的configureService 方法里注册JWT Authetication,为Swagger增加JWT Auth支持
            // Register the Swagger generator, defining one or more Swagger documents
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "API", Version = "v1" });
                //启用auth支持      
                var security = new Dictionary<string, IEnumerable<string>> { { "Bearer", new string[] { } }, };
                c.AddSecurityRequirement(security);
                c.AddSecurityDefinition("Bearer", new ApiKeyScheme
                { Description = "JWT Authorization header using the Bearer scheme. Example: "Authorization: Bearer {token}"",
                    Name = "Authorization",
                    In = "header",
                    Type = "apiKey"
                });

            });
            services.AddAuthentication(options=> {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer = "Bearer",
                    ValidAudience = "wr",
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtHelper.secretKey)),
                    RequireSignedTokens = true,
                    // 将下面两个参数设置为false,可以不验证Issuer和Audience,但是不建议这样做。
                    ValidateAudience = false,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    // 是否要求Token的Claims中必须包含 Expires
                    RequireExpirationTime = true,
                    // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                    ValidateLifetime = true
                };
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
                options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
                options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin,Client").Build());
            });

2. 在Configure方法里,useMVC的前面增加中间件,这样每次Web请求时,会先到 JwtTokenAuth 这里处理验证token

            app.UseMiddleware<JwtTokenAuth>();
            app.UseMvc();

3. JwtHelper.cs 里包含颁发和验证token2个方法

        public static string IssueJWT(TokenModelJWT tokenModel)
        {
            var dateTime = DateTime.UtcNow;
            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Jti,tokenModel.Uid.ToString()),//Id
                new Claim("Role", tokenModel.Role),//角色
                new Claim(JwtRegisteredClaimNames.Iat,dateTime.ToString(),ClaimValueTypes.Integer64)
            };
            //秘钥
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtHelper.secretKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: "Bearer",
                claims: claims, //声明集合
                expires: dateTime.AddHours(2),
                signingCredentials: creds);

            var jwtHandler = new JwtSecurityTokenHandler();
            var encodedJwt = jwtHandler.WriteToken(jwt);

            return encodedJwt;
        }
public static TokenModelJWT SerializeJWT(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            if (jwtToken.ValidTo < DateTime.UtcNow)
                return null;

            object role = new object(); ;
            try
            {
                jwtToken.Payload.TryGetValue("Role", out role);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var tm = new TokenModelJWT
            {
                Uid = int.Parse(jwtToken.Id),
                Role = role != null ? role.ToString() : "",
            };
            return tm;
        }

 

4. JwtTokenAuth.cs 中间件验证token,并增加User到httpContext,表示已登录

 

        public Task Invoke(HttpContext httpContext)
        {
            //检测是否包含‘Authorization‘请求头
            if (!httpContext.Request.Headers.ContainsKey("Authorization"))
            {
                return _next(httpContext);
            }
            var tokenHeader = httpContext.Request.Headers["Authorization"].ToString();

            TokenModelJWT tm = JwtHelper.SerializeJWT(tokenHeader);
            if (tm != null)
            {
                //授权
                var claimList = new List<Claim>();
                var claim = new Claim(ClaimTypes.Role, tm.Role);
                claimList.Add(claim);
                var identity = new ClaimsIdentity(claimList);
                var principal = new ClaimsPrincipal(identity);
                httpContext.User = principal;
            }


            return _next(httpContext);
        }

 

以上是关于把旧系统迁移到.Net Core 2.0 日记 (18) --JWT 认证(Json Web Token)的主要内容,如果未能解决你的问题,请参考以下文章

从 ASP.NET Core 1.1 MVC 迁移到 2.0 后,自定义 cookie 身份验证不起作用

使用 System.IdentityModel.Tokens.Jwt 从 1.1 迁移到 2.0 后,JWTAuthentication 在 asp.net core 2.0 中不起作用 - 5.1.

ASP.Net Core项目在Mac上使用Entity Framework Core 2.0进行迁移可能会遇到的一个问题.

带你做 WebAPI 迁移 ASP.NET Core 2.0

.NET Core 2.0迁移技巧之MemoryCache问题修复

.net core 2.0学习笔记:Remoting核心类库RealProxy迁移