跳至主要內容

NET和NET Core使用JWT授权验证

Dennis...约 2125 字大约 7 分钟NetJWT

JWT介绍

参考文章 https://www.cnblogs.com/cjsblog/p/9277677.htmlopen in new window

一、.NET 中使用

1. NuGet包

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

2. 自定义帮助类

2.1 新建interface接口

IHttpResponseResult

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

2.3 新建模型类

JwtToken

    /// <summary>
    /// Jwt Token
    /// </summary>
    public class JwtToken
    {
        /// <summary>
        /// 授权者
        /// </summary>
        public string AuthUserName { get; set; }

        /// <summary>
        /// Token过期时间
        /// </summary>
        public long ExpireTime { get; set; }

        /// <summary>
        /// Issuer
        /// </summary>
        public string Issuer { get; set; }
    }

2.4 新建Jwt帮助类

JwtHelper

using System;
using System.Text;
using GrpcCommon.Models;
using JWT;
using JWT.Algorithms;
using JWT.Exceptions;
using JWT.Serializers;

#pragma warning disable CS0618

namespace GrpcCommon.Helpers
{
    public class JwtHelper
    {
        private static readonly string secretKey = AppConfigs.SecurityKey;
        private static readonly string issuer = AppConfigs.Issuer;
        private static readonly int expire = AppConfigs.Expire;

        /// <summary>
        /// 颁发JWT Token
        /// </summary>
        /// <param name="securityKey"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        public static string GenerateJwt(string userName)
        {
            //身份验证信息
            var expTime = new DateTimeOffset(DateTime.Now.AddHours(expire)).ToUnixTimeSeconds();
            var jwtToken = new JwtToken { AuthUserName = userName, ExpireTime = expTime, Issuer = issuer };
            var key = Encoding.UTF8.GetBytes(securityKey);
            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); //生成令牌

            return token;
        }

        /// <summary>
        /// 校验解析Jwt Token
        /// </summary>
        /// <returns></returns>
        public static Tuple<bool, string> ValidateJwt(string token)
        {
            try
            {
                IJsonSerializer serializer = new JsonNetSerializer();
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtAlgorithm alg = new HMACSHA256Algorithm();
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, alg);
                var payLoad = decoder.Decode(token, secretKey);

                //校验通过,返回解密后的字符串
                return new Tuple<bool, string>(true, payLoad);
            }
            catch (TokenExpiredException expired)
            {
                //token过期
                return new Tuple<bool, string>(false, expired.Message);
            }
            catch (SignatureVerificationException sve)
            {
                //签名无效
                return new Tuple<bool, string>(false, sve.Message);
            }
            catch (Exception err)
            {
                // 解析出错
                return new Tuple<bool, string>(false, err.Message);
            }
        }
    }
}

3.配置API接口

3.1 新建Conteoller

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

    /// <summary>
    /// JWT授权
    /// </summary>
    [RoutePrefix("api/login")]
    public class LoginController : ApiController
    {
        private readonly string secretKey = AppConfigs.SecurityKey; //Jwt密钥,自定义

        /// <summary>
        /// 授权登录获取Token
        /// </summary>
        /// <param name="loginModel"></param>
        /// <returns></returns>
        [HttpPost, Route("GetToken")]
        public IHttpResponseResult Login([FromBody] Login loginModel)
        {
            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(null, "The user is not found or password is error.");
                }

                var token = JwtHelper.GenerateJwt("Dennis"); //生成令牌
                return HttpResponseResult.Success(token, "Get Token Success.");
            }
            catch (Exception e)
            {
                return HttpResponseResult.Fail(null, e.Message);
            }
        }
    }

4. 类验证Token

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

    /// <summary>
    /// Jwt 授权验证
    /// </summary>
    public class ApiAuthAttribute : AuthorizeAttribute
    {
        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 tokenRes = JwtHelper.ValidateJwt(token);
                return tokenRes.Item1;
            }
            catch (Exception e)
            {
                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. 配置授权认证

注册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 =>
                {
                    // token验证参数
                    o.TokenValidationParameters = new TokenValidationParameters
                    {
                        // 验证秘钥
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfig.SecretKey)),
                        // 验证颁发者
                        ValidateIssuer = true,
                        ValidIssuer = AppConfig.Issuer,
                        // 验证订阅者
                        ValidateAudience = true,
                        ValidAudience = AppConfig.Audience,
                        // 验证过期时间必须设置该属性
                        ClockSkew = TimeSpan.Zero
                    };

                    // 默认有www-authenticate响应头提示验证失败信息
                });

            // 如果需要角色控制到Action则需要配置Policy
            // 如果没有配置AddPolicy,接口直接使用[Authorize]特性即可
            // 如果接口只允许Admin或System角色的Token访问,则需要添加了[Authorize("SystemOrAdmin")]特性
            // 详细请测试LoginController的ParseToken的方法
            services.AddAuthorization(options =>
            {
                options.AddPolicy("User", policy => policy.RequireRole("User"));
                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 新建Claim枚举

JwtClaimKey


    /// <summary>
    /// Jwt Claim Key
    /// </summary>
    public class JwtClaimKey
    {
        /// <summary>
        /// userId
        /// </summary>
        public static string UserId = "userId";

        /// <summary>
        /// userName
        /// </summary>
        public static string UserName = "userName";
    }

3.2 新建token模型类

JwtTokenPayload

    /// <summary>
    /// Jwt Token Payload
    /// </summary>
    public class JwtTokenPayload
    {
        /// <summary>
        /// UserId
        /// </summary>
        public string UserId { get; set; }

        /// <summary>
        /// UserName
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// UserRoles
        /// </summary>
        public IEnumerable<string> UserRoles { get; set; }
    }

3.3 新建JWT帮助类

    /// <summary>
    /// JWT 帮助类
    /// </summary>
    public static class JwtHelper
    {
        private static readonly string Iss = AppConfig.Issuer;
        private static readonly string Aud = AppConfig.Audience;
        private static readonly string SecretKey = AppConfig.SecretKey;
        private static readonly int Expire = AppConfig.Expire;

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="userRoles"></param>
        /// <param name="payload"></param>
        /// <returns></returns>
        public static string GenerateJwt(IEnumerable<string> userRoles, JwtTokenPayload payload)
        {
            var claims = new List<Claim>
            {
                new Claim(JwtClaimKey.UserId, payload.UserId),
                new Claim(JwtClaimKey.UserName, payload.UserName)
            };

            // 可以将一个用户的多个角色全部赋予,比如参数System,Admin,那么该token即拥有两个角色
            claims.AddRange(userRoles.Select(role => new Claim(ClaimTypes.Role, role)));

            //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: Iss,
                audience: Aud,
                claims: claims,
                expires: DateTime.Now.AddHours(Expire),
                signingCredentials: credentials);

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

            return encodedJwt;
        }

        /// <summary>
        /// 解析Token
        /// </summary>
        /// <param name="jwtToken"></param>
        /// <returns></returns>
        public static Tuple<bool, JwtTokenPayload> ValidateJwt(string jwtToken)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            var token = jwtHandler.ReadJwtToken(jwtToken);

            try
            {
                var userId = token.Payload.GetValueOrDefault(JwtClaimKey.UserId).ToString();
                var userName = token.Payload.GetValueOrDefault(JwtClaimKey.UserName).ToString();
                var roles = token.Payload.GetValueOrDefault(ClaimTypes.Role);
                var roleList = new List<string>();

                switch (roles)
                {
                    case string _:
                        roleList = new List<string> { roles.ToString() };
                        break;
                    default:
                        var a = JsonConvert.DeserializeObject<JArray>(roles.ToString());
                        roleList.AddRange(a.Select(obj => obj.Value<string>()));
                        break;
                }

                var payLoad = new JwtTokenPayload
                {
                    UserId = userId,
                    UserName = userName,
                    UserRoles = roleList
                };
                return new Tuple<bool, JwtTokenPayload>(false, payLoad);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return new Tuple<bool, JwtTokenPayload>(false, null);
            }
        }
    }

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

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

4. 生成和测试Token

4.1 新建模型类

RoleModel

    /// <summary>
    /// 用户角色
    /// </summary>
    public class RoleModel
    {
        /// <summary>
        /// 角色
        /// </summary>
        public IEnumerable<string> UserRoles { get; set; }
    }

4.2 新建Api控制器

LoginController

    /// <summary>
    /// 首页
    /// </summary>
    [Route("api/login/[action]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        /// <summary>
        /// 登录获取Token
        /// </summary>
        /// <param name="role"></param>
        /// <returns></returns>
        [HttpPost]
        public IActionResult Login([FromBody] RoleModel role)
        {
            string token;
            if (role.UserRoles.Null())
            {
                token = "参数UserRoles为null";
                return BadRequest(new { Error = token });
            }

            var payLoad = new JwtTokenPayload
            {
                UserId = Guid.NewGuid().ToString(),
                UserName = "Dennis"
            };
            token = JwtHelper.GenerateJwt(role.UserRoles, payLoad);
            return Ok(new { Token = token });
        }

        /// <summary>
        /// 解析Token
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //[Authorize("SystemOrAdmin")]
        [Authorize]
        public IActionResult ParseToken()
        {
            //需要截取Bearer 
            var tokenHeader = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
            var tokenRes = JwtHelper.ValidateJwt(tokenHeader);
            return Ok(tokenRes.Item2);
        }
    }

三、源码地址

Gitee:https://gitee.com/dennisdong/net-coreapi-demoopen in new window

四、Swagger配合JWT使用

请参考文章:https://www.dennisdong.top/archives/2021/Net和Net-Core集成Swagger以及配合JWT身份验证.htmlopen in new window

五、WebAPI 跨域问题

请参考文章:https://www.dennisdong.top/archives/2021/NET-WebAPI-跨域问题(CORS-policy-No-Access-Control-Allow-Ogigin).htmlopen in new window

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.6