zoukankan      html  css  js  c++  java
  • .NET 和 .NET Core 使用 JWT 授权验证

    JWT介绍

    参考文章 https://www.cnblogs.com/cjsblog/p/9277677.html

    .NET 中使用

    1. NuGet包

    搜索JWT,下载安装(本人用的是8.2.3版本)

    2. 自定义帮助类

    2.1 新建 IHttpResponseResult interface接口

        public interface IHttpResponseResult
        {
        }
    
        /// <summary>
        /// 响应数据输出泛型接口
        /// </summary>
        /// <typeparam name="T"></typeparam>
        // ReSharper disable once UnusedTypeParameter
        public interface IHttpResponseResult<T> : IHttpResponseResult
        {
        }
    

    2.2 新建 HttpResponseResult 数据响应类

        public class HttpResponseResult<T> : IHttpResponseResult<T>
        {
            /// <summary>
            /// 状态码
            /// </summary>
            public int Code { get; set; }
    
            /// <summary>
            /// 消息
            /// </summary>
            public string Message { get; set; }
    
            /// <summary>
            /// 数据
            /// </summary>
            public T Data { get; set; }
    
            /// <summary>
            /// 成功
            /// </summary>
            /// <param name="data">数据</param>
            /// <param name="msg">消息</param>
            public HttpResponseResult<T> Success(T data = default, string msg = null)
            {
                Code = 0;
                Data = data;
                Message = msg;
                return this;
            }
    
            /// <summary>
            /// 失败
            /// </summary>
            /// <param name="code">状态码</param>
            /// <param name="msg">消息</param>
            /// <param name="data">数据</param>
            /// <returns></returns>
            public HttpResponseResult<T> Fail(T data = default, int code = -1, string msg = null)
            {
                Code = code;
                Message = msg;
                Data = data;
                return this;
            }
        }
    
        /// <summary>
        /// 响应数据静态输出
        /// </summary>
        public static class HttpResponseResult
        {
            /// <summary>
            /// 成功
            /// </summary>
            /// <param name="data">数据</param>
            /// <param name="msg">消息</param>
            /// <returns></returns>
            public static IHttpResponseResult Success<T>(T data, string msg = "message")
            {
                return new HttpResponseResult<T>().Success(data, msg);
            }
    
            /// <summary>
            /// 失败
            /// </summary>
            /// <param name="data">数据</param>
            /// <param name="msg">消息</param>
            /// <param name="code">状态码</param>
            /// <returns></returns>
            public static IHttpResponseResult Fail<T>(T data, string msg = null, int code = -1)
            {
                return new HttpResponseResult<T>().Fail(data, code, msg);
            }
        }
    

    3.生成JWT Token

    3.1 新建Conteoller

    新建 LoginController 并且新建一个生成 Token 的方法 Login

        /// <summary>
        /// JWT授权
        /// </summary>
        [RoutePrefix("api/login")]
        public class LoginController : ApiController
        {
            private readonly string secretKey = "JwtSecretKey";//Jwt密钥,自定义
            private readonly int tokenExpire = 2;//Token过期时间
    
            /// <summary>
            /// 授权登录获取Token
            /// </summary>
            /// <param name="loginModel"></param>
            /// <returns></returns>
            [HttpPost, Route("GetToken")]
            public IHttpResponseResult Login([FromBody] Login loginModel)
            {
                dynamic obj = new ExpandoObject();
                try
                {
                    var userName = loginModel.UserName;
                    var userPwd = loginModel.UserPwd;
                    //var loginRes = ChatUserDao.GetJwtUser(userName, userPwd).IsNull(); //自己的数据库验证
                    var loginRes = "Dennis".Equals(userName) && "123".Equals(userPwd);
                    if (loginRes)
                    {
                        return HttpResponseResult.Fail(obj, "The user is not found or password is error.");
                    }
    
                    var expireTime = DateTime.Now.AddHours(tokenExpire);
                    //身份验证信息
                    var jwtToken = new JwtToken { AuthUserName = userName, ExpireTime = expireTime };
                    var key = Encoding.UTF8.GetBytes(secretKey);
                    IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
                    IJsonSerializer serializer = new JsonNetSerializer(); //序列化Json
                    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //base64加解密
                    IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); //JWT编码
                    var token = encoder.Encode(jwtToken, key); //生成令牌
    
                    obj.Token = token;
                    obj.ExpireTime = expireTime;
    
                    return HttpResponseResult.Success(obj, "Get Token Success.");
                }
                catch (Exception e)
                {
                    LogFileHelper.WriteLog("GetToken", e.ToExceptionString());
                    return HttpResponseResult.Fail(obj, e.ToExceptionString());
                }
            }
        }
    

    3.2 新建JwtToken 模型类

        /// <summary>
        /// Jwt Token
        /// </summary>
        public class JwtToken
        {
            /// <summary>
            /// 授权者
            /// </summary>
            public string AuthUserName { get; set; }
    
            /// <summary>
            /// Token过期时间
            /// </summary>
            public DateTime ExpireTime { get; set; }
        }
    

    4. 新建Attribute类验证Token

    App_Start项目文件加中新建一个ApiAuthAttribute类,继承自AuthorizeAttribute

        /// <summary>
        /// Jwt 授权验证
        /// </summary>
        public class ApiAuthAttribute : AuthorizeAttribute
        {
            private readonly string secretKey = "JwtSecretKey";//加密秘钥,与生成Token时的密钥相同
            private const string authHeader = "Authorization";//请求头Header中存放JwtToken的Key名称
    
            /// <summary>
            /// 判断授权
            /// </summary>
            /// <param name="httpContext"></param>
            /// <returns></returns>
            protected override bool IsAuthorized(HttpActionContext httpContext)
            {
                try
                {
                    var httpHeader = httpContext.Request.Headers;
                    var token = string.Empty;//获取token
    
                    foreach (var keyHeader in httpHeader)
                    {
                        if (authHeader.Equals(keyHeader.Key))
                        {
                            token = keyHeader.Value.FirstOrDefault();
                        }
                    }
    
                    if (token.IsEmpty())
                    {
                        return false;
                    }
    
                    var key = Encoding.UTF8.GetBytes(secretKey);
                    IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
                    IJsonSerializer serializer = new JsonNetSerializer();//序列化Json
                    IDateTimeProvider provider = new UtcDateTimeProvider();
                    IJwtValidator validator = new JwtValidator(serializer, provider);
                    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
                    IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
    
                    //解密
                    var json = decoder.DecodeToObject<JwtToken>(token, key, true);
                    if (json.IsNull()) return false;
                    return json.ExpireTime >= DateTime.Now;
                }
                catch (Exception e)
                {
                    LogFileHelper.WriteLog(authHeader, e.ToExceptionString());
                    return false;
                }
            }
    
            /// <summary>
            /// 授权失败时调用
            /// </summary>
            /// <param name="httpContext"></param>
            protected override void HandleUnauthorizedRequest(HttpActionContext httpContext)
            {
                //Token过期时,响应头添加过期标识
                httpContext.Response = httpContext.ControllerContext.Request.CreateResponse(
                    HttpStatusCode.Unauthorized, HttpResponseResult.Fail(false, "Token is not found or expired, authorization failed."));
                httpContext.Response.Headers.Add("Token-Expired", "true");
            }
        }
    

    5. 测试Token验证

        /// <summary>
        /// 测试接口
        /// </summary>
        [RoutePrefix("api/test")]
        public class TestController : ApiController
        {
            /// <summary>
            /// Token认证测试
            /// </summary>
            /// <returns></returns>
            [HttpGet, Route("TokenAuth")]
            [ApiAuth]
            public IHttpResponseResult TokenAuth()
            {
                return HttpResponseResult.Success(true, "Auth Passed");
            }
        }
    

    .NET Core 中使用

    1. NuGet包

    搜索JwtBearer,下载安装(本人安装的是5.0.7)

    2. 配置Startup

    注册JWT中间件,我是单独写了一个类然后引用,你也可以直接写在Startup的ConfigureServices方法中

        /// <summary>
        /// JWT授权中间件
        /// </summary>
        public static class AuthorizationMiddleware
        {
            /// <summary>
            /// 注册授权服务
            /// </summary>
            /// <param name="services"></param>
            public static void AddAuthorizationService(this IServiceCollection services)
            {
                // 开启Bearer认证
                services.AddAuthentication(options =>
                    {
                        // 设置默认使用jwt验证方式
                        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    })
                    // 添加JwtBearer服务
                    .AddJwtBearer(o =>
                    {
                        // 令牌验证参数
                        o.TokenValidationParameters = new TokenValidationParameters
                        {
                            // 设置生成token的秘钥
                            //IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(AppConfig.SecretKey)),
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfig.SecretKey)),
                            // 验证秘钥
                            ValidateIssuerSigningKey = true,
                            // 验证发布者
                            ValidateIssuer = true,
                            // 验证Issure
                            ValidIssuer = AppConfig.Issuer,//发行人
                            // 验证接收者
                            ValidateAudience = true,
                            // 读配置Audience
                            ValidAudience = AppConfig.Audience,//订阅人
                            // 验证过期时间
                            ValidateLifetime = true,
                            ClockSkew = TimeSpan.FromSeconds(30),
                            RequireExpirationTime = true
                        };
                        o.Events = new JwtBearerEvents
                        {
                            OnAuthenticationFailed = context =>
                            {
                                // 如果过期,则把<是否过期>添加到,返回头信息中
                                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                                {
                                    context.Response.Headers.Add("Token-Expired", "true");
                                }
    
                                return Task.CompletedTask;
                            }
                        };
                    });
    
                //如果没有角色控制到Action则注释下列代码
                services.AddAuthorization(options =>
                {
                    options.AddPolicy("User", policy => policy.RequireRole("User").Build());
                    options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
    
                });
            }
        }
    

    然后在Startup的ConfigureServices方法中添加引用
    services.AddAuthorizationService();

    在Startup的Configure方法中使用授权
    app.UseRouting()之后和app.UseEndpoints之前添加代码

    app.UseAuthentication();//身份验证
    app.UseAuthorization();//身份授权
    

    3. JWT自定义帮助类

    3.1 JWTHelper

        /// <summary>
        /// JWT 帮助类
        /// </summary>
        public static class JWTHelper
        {
            /// <summary>
            /// 颁发JWT字符串
            /// </summary>
            /// <param name="tokenModel"></param>
            /// <returns></returns>
            public static string IssueJwt(UserModel tokenModel)
            {
                //获取Appsetting配置
                var iss = AppConfig.Issuer;
                var aud = AppConfig.Audience;
                var secret = AppConfig.SecretKey;
                var expire = AppConfig.Expire;
    
                //var claims = new Claim[] //old
                var claims = new List<Claim>
                {
                    /*
                    * 特别重要:
                      1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
                      2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
                    */
    
                    new Claim(JwtRegisteredClaimNames.Jti, tokenModel.UserId),
                    new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                    new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                    //这个就是过期时间,目前是过期1000秒,可自定义,注意JWT有自己的缓冲过期时间
                    new Claim(JwtRegisteredClaimNames.Exp,
                        $"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.Expiration, DateTime.Now.AddMinutes(expire).ToFormatString()),
                    new Claim(JwtRegisteredClaimNames.Iss, iss),
                    new Claim(JwtRegisteredClaimNames.Aud, aud)
                };
    
                // 可以将一个用户的多个角色全部赋予,比如参数System,Admin,那么该token即拥有两个角色;
                claims.AddRange(tokenModel.UserName.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
    
                //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
                var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
                var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
                var jwt = new JwtSecurityToken(iss, claims: claims, signingCredentials: credentials);
    
                var jwtHandler = new JwtSecurityTokenHandler();
                var encodedJwt = jwtHandler.WriteToken(jwt);
    
                return encodedJwt;
            }
    
            /// <summary>
            /// 解析
            /// </summary>
            /// <param name="jwtStr"></param>
            /// <returns></returns>
            public static UserModel SerializeJwt(string jwtStr)
            {
                var jwtHandler = new JwtSecurityTokenHandler();
                var jwtToken = jwtHandler.ReadJwtToken(jwtStr);
                object role;
                try
                {
                    jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
                var tm = new UserModel
                {
                    UserId = jwtToken.Id,
                    UserRole = role != null ? role.ToString() : "",
                };
                return tm;
            }
        }
    

    密钥等相关配置动态配置在appsettings.json文件中

      "JwtSetting": {
        "Issuer": "DennisDong",
        "Audience": "https://www.dennisdong.top",
        "SecretKey": "D@1#n$n%i&s.D*0n!g",
        "Expire": "2"
      }
    

    3.2 UserModel

        /// <summary>
        /// 用户Model
        /// </summary>
        public class UserModel
        {
            /// <summary>
            /// UserId
            /// </summary>
            public string UserId { get; set; }
    
            /// <summary>
            /// 角色
            /// </summary>
            public string UserRole { get; set; }
    
            /// <summary>
            /// 姓名
            /// </summary>
            public string UserName { get; set; }
    
            /// <summary>
            /// 生日
            /// </summary>
            public DateTime UserBirthDay { get; set; }
        }
    

    4. 生成和测试Token

        /// <summary>
        /// 登录
        /// </summary>
        [Route("api/login/[action]")]
        [ApiController]
        public class LoginController : ControllerBase
        {
            /// <summary>
            /// 登录获取Token
            /// </summary>
            /// <param name="role"></param>
            /// <returns></returns>
            [HttpGet]
            public IActionResult Login(string role)
            {
                string token;
                var result = false;
                if (role.NotNull())
                {
                    //这里只是测试,具体数据验证根据自己需要来即可
                    token = JWTHelper.IssueJwt(new UserModel
                    {
                        UserId = Guid.NewGuid().ToString(), 
                        UserRole = role, 
                        UserName = role,
                        UserBirthDay = DateTime.Now
                    });
                    result = true;
                }
                else
                {
                    token = " Login Fail.";
                }
    
                return Ok(new { Status = result, Token = token });
            }
    
            /// <summary>
            /// 解析Token
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            //该接口限制只有System 或 Admin 角色(中间件中AddAuthorization方法的配置决定)的Token可以访问
            [Authorize("SystemOrAdmin")]
            //如果中间件中没有配置AddPolicy,直接使用Authorize即可
            //[Authorize]
            public IActionResult ParseToken()
            {
                //需要截取Bearer 
                var tokenHeader = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
                var user = JWTHelper.SerializeJwt(tokenHeader);
                return Ok(user);
            }
        }
    

    Swagger配合JWT使用

    请参考文章 https://www.cnblogs.com/dennisdong/p/15719616.html

    WebAPI 跨域问题

    请参考文章 https://www.cnblogs.com/dennisdong/p/15719873.html

    源码下载

    gitee:https://gitee.com/dennisdong/net-coreapi-demo

  • 相关阅读:
    横向技术分析C#、C++和Java优劣
    XML数据流主动服务系统的设计与实现
    ASP.NET 状态管理
    Oracle中如何从BasicFile迁移到SecureFile
    让UpdatePanel支持上传文件
    C#和Visual Basic渐行渐远之匿名类型
    wpf 移动变换
    WPF 样式学习总结
    临江仙
    wpf 动画效果
  • 原文地址:https://www.cnblogs.com/dennisdong/p/15719005.html
Copyright © 2011-2022 走看看