https://www.cnblogs.com/datacool/p/datacool_dotnetcore_demo.html
實踐技術看點
- 1、Swagger管理API說明文檔
- 2、JwtBearer token驗證
- 3、Swagger UI增加Authentication
- 4、EntityFrameworkCore+MySQL
- 5、在.net core 3.1下使用Log4net
前言
元旦過后就沒什么工作上的任務了,這當然不能讓領導看在眼里,動手實踐一下新技術吧。於是准備搭一個webapi的中間件框架。
由於自己的雲主機是台linux服務器,雙核2G的centos+1M 沒有數據盤,也用不起RDS,如果裝個Windows Server那么肯定卡的不行,所以一直想嘗試一下跨平台的感覺。
由於這篇隨筆不是定位於教程,故基礎知識一概略過。
項目中使用到的包清單:
<ItemGroup>
<PackageReference Include="IdentityModel" Version="4.1.1" />
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
<PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.19" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.0.0" />
</ItemGroup>
關鍵代碼點評
1)Startup
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using IdentityModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using MySql.Data.EntityFrameworkCore.Extensions;
using Swashbuckle.AspNetCore.Swagger;
using tokendemo.Models;
namespace tokendemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1",
new OpenApiInfo
{
Title = "XXX項目接口文檔",
Version = "v1",
Contact = new OpenApiContact
{
Email = "xyf_xiao@cquni.com",
Name = "肖遠峰",
Url = new Uri("http://datacool.cnblogs.com")
}
});
// 為 Swagger 設置xml文檔注釋路徑
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("Bearer",
new OpenApiSecurityScheme
{
Description = "請輸入OAuth接口返回的Token,前置Bearer。示例:Bearer {Roken}",
Name = "Authorization",
In = ParameterLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中)
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
}, Array.Empty<string>()
}
});
});
var posdbConnString = Configuration.GetConnectionString("POS_Db");
services.AddDbContext<posdbContext>(option =>
{
option.UseMySql(posdbConnString, null);
});
services.AddScoped<IAuthenticateService, TokenAuthenticationService>();
services.AddScoped<IUserService, UserService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
//啟用中間件服務生成SwaggerUI,指定Swagger JSON終結點
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "XXX接口文檔 V1");
c.RoutePrefix = string.Empty;//設置根節點訪問
});
app.UseLog4net();
}
}
}
using Microsoft.AspNetCore.Builder;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace tokendemo
{
public static class LoggeServiceExt
{
/// 使用log4net配置
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseLog4net(this IApplicationBuilder app)
{
var logRepository = log4net.LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy));
log4net.Config.XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
return app;
}
}
}
public interface IUserService
{
bool IsValid(LoginRequestDTO req);
}
public interface IAuthenticateService
{
bool IsAuthenticated(LoginRequestDTO request, out string token);
}
public class UserService : IUserService
{
public bool IsValid(LoginRequestDTO req)
{
return true;
}
}
public class TokenAuthenticationService : IAuthenticateService
{
private readonly IUserService _userService;
private readonly TokenManagement _tokenManagement;
private readonly posdbContext db;
public TokenAuthenticationService(IUserService userService, IOptions<TokenManagement> tokenManagement, posdbContext posdb)
{
_userService = userService;
_tokenManagement = tokenManagement.Value;
db = posdb;
}
public string GetAuthentUser()
{
return JsonConvert.SerializeObject(db.SysApiAuthorize.ToList());
}
public bool IsAuthenticated(LoginRequestDTO request, out string token)
{
token = string.Empty;
if (!_userService.IsValid(request))
return false;
var claims = new[]
{
new Claim(ClaimTypes.Name,request.Username)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials);
token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return true;
}
}
token驗證是我關注的重點,而Swagger支持查看文檔的同時調用API,也支持授權認證,所以水到渠成。代碼命名都是比較規范的,當然大部分來源於別人的文章,這里就不作過多說明了。
asp.net core對依賴注入思想是貫徹始終的,新人需要在這個思想的領悟上下苦功夫才能駕馭她。
2)配置文件
appsettings
log4net.config
Scaffold-DbContext "server=localhost;userid=root;pwd=dba#2020;port=3306;database=posdb;sslmode=none;" Pomelo.EntityFrameworkCore.MySql -OutputDir Models -Force
由於我的數據庫是先存在了,所以直接使用了nutget控制台生成了數據庫上下文對象和實體。注意向導生成的數據庫上下文里是把數據庫連接字符串寫死的,需要修改。本例是寫入appsettings.json里的。請重點看一下上面的配置和Startup里獲取配置的代碼。
3)關聯代碼,幾個數據傳輸類
public class TokenManagement
{
[JsonProperty("secret")]
public string Secret { get; set; }
[JsonProperty("issuer")]
public string Issuer { get; set; }
[JsonProperty("audience")]
public string Audience { get; set; }
[JsonProperty("accessExpiration")]
public int AccessExpiration { get; set; }
[JsonProperty("refreshExpiration")]
public int RefreshExpiration { get; set; }
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
public class LoginRequestDTO
{
[Required]
[JsonProperty("username")]
public string Username { get; set; }
[Required]
[JsonProperty("password")]
public string Password { get; set; }
}
3)API控制器
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Threading.Tasks;
5 using log4net;
6 using Microsoft.AspNetCore.Authorization;
7 using Microsoft.AspNetCore.Mvc;
8
9 namespace tokendemo.Controllers
10 {
11 [ApiController]
12 [Route("[controller]")]
13 [Authorize]
14 public class WeatherForecastController : ControllerBase
15 {
16 private readonly ILog _logger;
17 public WeatherForecastController()
18 {
19 _logger = LogManager.GetLogger(typeof(WeatherForecastController));
20 }
21 private static readonly string[] Summaries = new[]
22 {
23 "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
24 };
25
26
27 [HttpGet]
28 public IEnumerable<WeatherForecast> Get()
29 {
30 var rng = new Random();
31 _logger.Info("OK");
32 return Enumerable.Range(1, 5).Select(index => new WeatherForecast
33 {
34 Date = DateTime.Now.AddDays(index),
35 TemperatureC = rng.Next(-20, 55),
36 Summary = Summaries[rng.Next(Summaries.Length)]
37 })
38 .ToArray();
39 }
40
41 }
42 }
這個大家應該很熟悉了,這就是vs2019向導創建的API控制器。[Authorize]標記會導致401錯誤,就是表示先要去獲取access token,在Header里帶入Bearer+空格+token才可以正常調用。

授權控制器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace tokendemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly IAuthenticateService _authService;
public AuthenticationController(IAuthenticateService service)
{
_authService = service;
}
[AllowAnonymous]
[HttpPost, Route("requestToken")]
public ActionResult RequestToken([FromBody] LoginRequestDTO request)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid Request");
}
string token;
var authTime = DateTime.UtcNow;
if (_authService.IsAuthenticated(request, out token))
{
return Ok(new
{
access_token = token,
token_type = "Bearer",
profile = new
{
sid = request.Username,
auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds()
}
});
}
return BadRequest("Invalid Request");
}
}
}
收獲與感想
- 1、妥妥的吃了次螃蟹,收獲了經驗
- 2、正在“為自己挖一口井”的路上
- 3、.net core算是入門了
- 4、源碼我是沒自信放到github的,后面會加上下載鏈接
- 5、伙計們分享起來吧,這個生態建設任重而道遠啊。
這里是源碼下載的地址:https://files.cnblogs.com/files/datacool/tokendemo.zip
"作者:" 數據酷軟件工作室
"出處:" http://datacool.cnblogs.com

