现在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配置 } }
每个类的功能介绍:
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需要使用一下。
写完直接运行
请求一下第一个接口:
直接请求第二个接口:提示401,认证失败
我们把登录接口返回的token加到右上角的授权里面去。
再来请求获取用户信息接口:已经成功了。
Token是无状态的,不需要存库。整个过程用的类比较多,实际上流程很简单,你可以把JWT理解为一个工具。
留下您的脚步
最近评论