技术 思绪 摘录 旅行
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。本文只是举个例子,可以这么整,如果要用于生产环境,可能得再封装一下,注释都加的齐全,没用过的可以看看。

现在Visual Studio2019升级到最新,就可以创建.NET5的项目了

直接选择ASP.NET Core Web应用程序,ASP.NET Core 5.0的版本。

创建好之后,我们加入以下配置项

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "JWTSetting": {
    "JWTIssuer": "JwtDemoApi", //JWT配置 签发者
    "JWTAudience": "JwtDemoApi", //JWT配置 接受者
    "JWTExpires": "30", //JWT配置 过期天数
    "JWTKey": "bc47a26eb9a59406057dddd62d0898f4" //JWT配置
  }
}

image.png

每个类的功能介绍:

    1、BaseController:这是基础控制器,可能会有公共方法,其他控制器继承他。

    2、AuthController:登录接口在里面。

    3、MemberController:其他需要授权的才能访问的接口在里面。

    4、ConfigHelper:封装一个读取配置项的辅助类。

    5、GetLoginDto:登录入参。

    6、LoginDto:登录回参。

    7、JwtUserInfo:ToKen中带的用户信息。

    8、JwtHelper:颁发Token的辅助类。

引用一下依赖项:

    1、Microsoft.AspNetCore.Authentication.JwtBearer

    2、Microsoft.AspNetCore.Mvc.NewtonsoftJson

    3、Swashbuckle.AspNetCore

    4、Swashbuckle.AspNetCore.Filters


发现StartUp.cs里面自带Swagger,不过我们得改下,最终改成下面这样:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.Filters;
using System;
using System.IO;
using System.Text;

namespace JwtDemoApi
{
    public class Startup
    {
        private readonly IConfiguration _configuration;

        /// <summary>
        /// Initializes a new instance of the <see cref="Startup"/> class.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        public Startup(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        /// <summary>
        /// Configures the services.
        /// </summary>
        /// <param name="services">The services.</param>
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //配置跨域
            services.AddCors(options => options.AddPolicy("any", builder => builder
                .WithOrigins("*")
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowAnyOrigin()));
            //swagger配置
            services.AddSwaggerGen(c =>
            {
                //配置swagge版本显示信息
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtDemoApi", Version = "v1", Description = "API描述信息" });
                //把xml文件都加进去
                foreach (var item in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.xml"))
                {
                    c.IncludeXmlComments(item, true);
                }
                //添加引用 Swashbuckle.AspNetCore.Filters
                c.OperationFilter<AddResponseHeadersFilter>();
                c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
                c.OperationFilter<SecurityRequirementsOperationFilter>();
                //添加请求头配置
                c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme()
                {
                    Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer Token",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.Http,
                    BearerFormat = "JWT",
                    Scheme = "Bearer"
                });
            });

            //配置认证服务
            //引用Microsoft.AspNetCore.Authentication.JwtBearer
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    //是否验证发行人
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    ValidateLifetime = true, //验证生命周期
                    ValidIssuer = _configuration.GetSection("JWTSetting").GetSection("JWTIssuer").Value,//发行人
                    ValidAudience = _configuration.GetSection("JWTSetting").GetSection("JWTAudience").Value,//受众人
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration.GetSection("JWTSetting").GetSection("JWTKey").Value)),
                    ClockSkew = TimeSpan.Zero,//缓冲时间
                };
            });
            //配置序列化 引用Microsoft.AspNetCore.Mvc.NewtonsoftJson
            services.AddControllers().AddNewtonsoftJson(setup =>
            {
                setup.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();//驼峰命名返回
                setup.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //忽略循环引用
                setup.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; //默认日期格式化
            });

        }

        /// <summary>
        /// Configures the specified application.
        /// </summary>
        /// <param name="app">The application.</param>
        /// <param name="env">The env.</param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JwtDemoApi v1"));
            }

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

这里swagger配置,有个注意点,jwt的Token我们采用Bearer这种方式,希望在Swagger请求自动带上Bearer前缀,那么SecuritySchemeType.Http这一句就至关重要。

本文例子打算创建两个控制器,一个负责登录,一个是其他业务接口,需要登录之后才能调用的。

开始看第一个控制器BaseController,这个控制器可能是有公用方法,所以里面的方法标识了,不是接口,忽略特性

[ApiExplorerSettings(IgnoreApi = true)]
using JwtDemoApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;

namespace JwtDemoApi.Controllers
{
    /// <summary>
    /// BaseController
    /// </summary>
    public class BaseController : ControllerBase
    {
        /// <summary>
        /// 从JwtToken获取用户信息
        /// </summary>
        /// <returns></returns>
        [ApiExplorerSettings(IgnoreApi = true)]//忽略
        [Authorize]
        public JwtUserInfo GetUseByJwtToken()
        {
            try
            {
                string token = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer", "").Trim();
                IEnumerable<Claim> clims = new JwtSecurityToken(token).Claims;
                return new JwtUserInfo()
                {
                    Id = clims.FirstOrDefault(z => z.Type == nameof(JwtUserInfo.Id))?.Value ?? "",
                    UserName = clims.FirstOrDefault(z => z.Type == nameof(JwtUserInfo.UserName))?.Value ?? "",
                    Mobile = clims.FirstOrDefault(z => z.Type == nameof(JwtUserInfo.Mobile))?.Value ?? "",
                    WxNickName = clims.FirstOrDefault(z => z.Type == nameof(JwtUserInfo.WxNickName))?.Value ?? "",
                };
            }
            catch (Exception ex)
            {
                return null;
            }
        }
    }
}

再看AuthController控制器,这里面只有登录接口

using JwtDemoApi.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace JwtDemoApi.Controllers
{
    /// <summary>
    /// 授权控制器
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : BaseController
    {
        /// <summary>
        ///登录获取Token
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        [HttpPost("Login")]
        public async Task<LoginDto> LoginAsync([FromBody] GetLoginDto model)
        {
            //TODO 数据库相关操作逻辑

            #region 组织jwt信息
            //组装jwt信息
            IEnumerable<Claim> claims = new Claim[] {
                new Claim(nameof(JwtUserInfo.UserName),"杨富贵"),
                new Claim(nameof(JwtUserInfo.WxNickName),"CarsonYang"),
                new Claim(nameof(JwtUserInfo.Mobile),"158****2846"),
                new Claim(nameof(JwtUserInfo.Id),"1"),
            };
            #endregion

            LoginDto retdata = new LoginDto
            {
                IsSucceed = true,
                Message = "登录成功",
                Token = JwtHelper.BuildJwtToken(claims),
                Expired = JwtHelper.GetTimeStamp(DateTime.UtcNow.AddDays(ConfigHelper.GetConfigToInt("JWTSetting:JWTExpires"))).ToString()
            };

            return retdata;
        }
    }
}

再看MemberController控制器,这个控制器里面的接口就需要登录之后才能操作了。

using JwtDemoApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace JwtDemoApi.Controllers
{
    /// <summary>
    /// 授权控制器
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class MemberController : BaseController
    {
        /// <summary>
        ///获取用户信息
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns></returns>
        [HttpPost("GetUserInfo")]
        [Authorize]
        public async Task<JwtUserInfo> GetUserInfoAsync([FromBody] GetLoginDto model)
        {
            //获取到登录人信息
            JwtUserInfo user = GetUseByJwtToken();
            //TODO 业务逻辑
            return user;
        }
    }
}

注意看多了个授权特性:

[Authorize]

接下来就是把其他辅助类和DTO写一下:

ConfigHelper.cs

using Microsoft.Extensions.Configuration;
using System;

namespace JwtDemoApi.Models
{
    public class ConfigHelper
    {
        /// <summary>
        /// The configuration
        /// </summary>
        private static IConfigurationRoot _configuration;

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigHelper"/> class.
        /// </summary>
        public ConfigHelper()
        {
            if (_configuration == null)
            {
                IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
                _configuration = builder.Build();
            }
        }

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public string GetConfig(string key)
        {
            return _configuration[key];
        }

        /// <summary>
        ///获取字符串配置
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static string GetConfigToString(string key)
        {
            try
            {
                return new ConfigHelper().GetConfig(key);
            }
            catch
            {
                return "";
            }
        }
        /// <summary>
        /// 查询配置,返回整数
        /// </summary>
        /// <param name="key">键名</param>
        /// <returns></returns>
        public static int GetConfigToInt(string key)
        {
            try
            {
                string configValue = new ConfigHelper().GetConfig(key);
                if (configValue == null)
                {
                    return int.MinValue;
                }
                return Convert.ToInt32(configValue);
            }
            catch
            {
                return int.MinValue;
            }
        }
        /// <summary>
        /// 查询配置,返回整数
        /// </summary>
        /// <param name="key">键名</param>
        /// <returns></returns>
        public static bool GetConfigToBool(string key)
        {
            try
            {
                string configValue = new ConfigHelper().GetConfig(key);
                if (configValue == null)
                {
                    return false;
                }
                return Convert.ToBoolean(configValue);
            }
            catch
            {
                return false;
            }
        }
    }
}

GetLoginDto.cs

namespace JwtDemoApi.Models
{
    /// <summary>
    /// 登录入参
    /// </summary>
    public class GetLoginDto
    {
        /// <summary>
        ///用户名
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        ///密码
        /// </summary>
        public string Pass { get; set; }
    }
}

JwtHelper.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JwtDemoApi.Models
{
    /// <summary>
    /// jwt操作类
    /// </summary>
    public class JwtHelper
    {
        /// <summary>
        /// 创建jwttoken
        /// </summary>
        /// <param name="claims">The claims.</param>
        /// <returns></returns>
        public static string BuildJwtToken(IEnumerable<Claim> claims)
        {
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ConfigHelper.GetConfigToString("JWTSetting:JWTKey")));

            DateTime expiresAt = DateTime.Now.AddDays(ConfigHelper.GetConfigToInt("JWTSetting:JWTExpires"));

            //将用户信息添加到 Claim 中
            var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);

            identity.AddClaims(claims);

            SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),//创建声明信息
                Issuer = ConfigHelper.GetConfigToString("JWTSetting:JWTIssuer"),//Jwt token 的签发者
                Audience = ConfigHelper.GetConfigToString("JWTSetting:JWTAudience"),//Jwt token 的接收者
                NotBefore = DateTime.Now,
                Expires = expiresAt,//过期时间
                SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)//创建 token
            };
            SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }

        /// <summary>
        /// 获取时间戳  13位
        /// </summary>
        /// <param name="dtime">The dtime.</param>
        /// <returns></returns>
        public static long GetTimeStamp(DateTime dtime)
        {
            TimeSpan ts = dtime - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds * 1000);
        }
    }
}

JwtUserInfo.cs 

namespace JwtDemoApi.Models
{
    /// <summary>
    /// token中的信息
    /// </summary>
    public class JwtUserInfo
    {
        /// <summary>
        ///用户ID
        /// </summary>
        public string Id { get; set; }

        /// <summary>
        ///用户昵称
        /// </summary>
        public string WxNickName { get; set; }

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

        /// <summary>
        /// 手机号
        /// </summary>
        public string Mobile { get; set; }
    }
}

LoginDto.cs

namespace JwtDemoApi.Models
{
    /// <summary>
    /// 登录回参
    /// </summary>
    public class LoginDto
    {
        /// <summary>
        ///是否成功
        /// </summary>
        public bool IsSucceed { get; set; }

        /// <summary>
        ///错误信息
        /// </summary>
        public string Message { get; set; }

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

        /// <summary>
        ///时间戳
        /// </summary>
        public string Expired { get; set; }
    }
}

项目右键->属性->生成xml,把xml生成了,swagger需要使用一下。

写完直接运行

image.png

请求一下第一个接口:

image.png

直接请求第二个接口:提示401,认证失败

image.png

我们把登录接口返回的token加到右上角的授权里面去。

image.png

再来请求获取用户信息接口:已经成功了。

image.png

Token是无状态的,不需要存库。整个过程用的类比较多,实际上流程很简单,你可以把JWT理解为一个工具。


CarsonIT 微信扫码关注公众号 策略、创意、技术

留下您的脚步

 

最近评论

查看更多>>

精选推荐

阅读排行

友情打赏

请打开您的微信,扫一扫