485 lines
15 KiB
Markdown
485 lines
15 KiB
Markdown
---
|
||
title: ASP.Net 6
|
||
date: 2021-03-23 10:30:31
|
||
author: 文永达
|
||
top_img: https://gcore.jsdelivr.net/gh/volantis-x/cdn-wallpaper/abstract/67239FBB-E15D-4F4F-8EE8-0F1C9F3C4E7C.jpeg
|
||
---
|
||
|
||
# ASP.Net 6
|
||
|
||
## 部署到Docker
|
||
|
||
### 安装.Net SDK 6.0环境
|
||
|
||
```shell
|
||
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
|
||
sudo yum install dotnet-sdk-6.0
|
||
dotnet --info
|
||
```
|
||
|
||
### Visual Studio添加Docker支持
|
||
|
||

|
||
|
||
### Linux下构建Docker镜像
|
||
|
||
```shell
|
||
docker image build -f ./XiaodaERP/Dockerfile -t aspnetcore .
|
||
docker images
|
||
```
|
||
|
||
### 运行Docker镜像
|
||
|
||
```shell
|
||
docker run --name=aspnetcore -p 9001:80 -d aspnetcore
|
||
docker ps
|
||
```
|
||
|
||
|
||
|
||
```shell
|
||
cd /usr/local/jenkins_home/workspace/XiaodaERP_NetCore
|
||
echo $PWD
|
||
docker image build -f ./XiaodaERP/Dockerfile -t xiaodaerp/netcore .
|
||
docker images
|
||
docker run --name xiaodaerp/netcore -p 7274:80 -d xiaodaerp/netcore
|
||
```
|
||
|
||
## 顶级语句配置`Program.cs`
|
||
|
||
### 取消默认JSON首字母小写命名
|
||
|
||
```c#
|
||
builder.Services.AddControllers().AddJsonOptions(options => {
|
||
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||
});
|
||
```
|
||
|
||
### Json序列化时忽略属性为null的值
|
||
|
||
```c#
|
||
builder.Services.AddControllers().AddJsonOptions(options => {
|
||
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
||
});
|
||
```
|
||
|
||
### 使用Autofac自动注入Service
|
||
|
||
通过NuGet包管理器 安装NuGet包
|
||
|
||

|
||
|
||

|
||
|
||
Autofac
|
||
|
||
Autofac.Extensions.DependencyInjection
|
||
|
||
Autofac.Extras.DynamicProxy
|
||
|
||
新建`ServiceAutofac.cs`类
|
||
|
||
```c#
|
||
using System.Reflection;
|
||
|
||
namespace XiaodaERP
|
||
{
|
||
public class ServiceAutofac
|
||
{
|
||
/// <summary>
|
||
/// 获取程序集名称
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public static string GetAssemblyName()
|
||
{
|
||
return Assembly.GetExecutingAssembly().GetName().Name;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`Program.cs`配置
|
||
|
||
```c#
|
||
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
|
||
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
|
||
{
|
||
Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
|
||
builder.RegisterAssemblyTypes(assembly)
|
||
.AsImplementedInterfaces()
|
||
.InstancePerDependency();
|
||
});
|
||
```
|
||
|
||
### 注入Entity Framework Core 6 DbContext上下文
|
||
|
||
```c#
|
||
builder.Services.AddDbContext<OracleDbContext>(options =>
|
||
options.UseOracle(builder.Configuration.GetConnectionString("OracleDbContext")));
|
||
|
||
builder.Services.AddDbContext<SqlServerDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServerDbContext")));
|
||
```
|
||
|
||
### 使用JWT进行授权与认证
|
||
|
||
安装NuGet包
|
||
|
||
`Microsoft.AspNetCore.Authentication.JwtBearer`
|
||
|
||

|
||
|
||
`appsettings.json`配置文件中配置
|
||
|
||
```json
|
||
"Authentication": {
|
||
"SecretKey": "nadjhfgkadshgoihfkajhkjdhsfaidkuahfhdksjaghidshyaukfhdjks",
|
||
"Issuer": "www.xiaoda",
|
||
"Audience": "www.xiaoda"
|
||
}
|
||
```
|
||
|
||
`Program.cs`顶级语句配置
|
||
|
||
```c#
|
||
// 使用Autofac自动注入Service
|
||
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
|
||
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
|
||
{
|
||
Assembly assembly = Assembly.Load(ServiceAutofac.GetAssemblyName());//注入Service程序集 可以是其他程序集
|
||
builder.RegisterAssemblyTypes(assembly)
|
||
.AsImplementedInterfaces()
|
||
.InstancePerDependency();
|
||
// 在IOC容器中注入
|
||
// 用于Jwt的各种操作
|
||
builder.RegisterType<JwtSecurityTokenHandler>().InstancePerLifetimeScope();
|
||
// 支持泛型存入Jwt
|
||
builder.RegisterType<TokenHelper>().InstancePerLifetimeScope();
|
||
});
|
||
|
||
//JWT认证
|
||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
|
||
{
|
||
//取出私钥
|
||
var secretByte = Encoding.UTF8.GetBytes(builder.Configuration["Authentication:SecretKey"]);
|
||
options.TokenValidationParameters = new TokenValidationParameters()
|
||
{
|
||
//验证发布者
|
||
ValidateIssuer = true,
|
||
ValidIssuer = builder.Configuration["Authentication:Issuer"],
|
||
//验证接受者
|
||
ValidateAudience = true,
|
||
ValidAudience = builder.Configuration["Authentication:Audience"],
|
||
//验证是否过期
|
||
ValidateLifetime = true,
|
||
//验证私钥
|
||
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
|
||
};
|
||
|
||
});
|
||
// 顺序不能颠倒
|
||
// 你是谁 授权
|
||
app.UseAuthentication();
|
||
// 你可以干什么 验证
|
||
app.UseAuthorization();
|
||
```
|
||
|
||
新建`TokenHelper.cs`工具类
|
||
|
||
```c#
|
||
using Microsoft.IdentityModel.Tokens;
|
||
using System.IdentityModel.Tokens.Jwt;
|
||
using System.Reflection;
|
||
using System.Security.Claims;
|
||
using System.Text;
|
||
using XiaodaERP.Models;
|
||
|
||
namespace XiaodaERP.Utils
|
||
{
|
||
public class TokenHelper
|
||
{
|
||
private readonly IConfiguration _configuration;
|
||
private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;
|
||
public TokenHelper(IConfiguration configuration, JwtSecurityTokenHandler jwtSecurityTokenHandler)
|
||
{
|
||
this._configuration = configuration;
|
||
this._jwtSecurityTokenHandler = jwtSecurityTokenHandler;
|
||
}
|
||
public static string? Token { get; set; }
|
||
// 生成Token
|
||
public string CreateJwtToken<T>(T user)
|
||
{
|
||
// 生成JWT
|
||
// Header,选择签名算法
|
||
var signingAlogorithm = SecurityAlgorithms.HmacSha256;
|
||
// Payload,存放用户信息,放用户ID,用户名
|
||
var claimList = this.CreateClaimList(user);
|
||
//Signature
|
||
//取出私钥并以utf8编码字节输出
|
||
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
|
||
//使用非对称算法对私钥进行加密
|
||
var signingKey = new SymmetricSecurityKey(secretByte);
|
||
//使用HmacSha256来验证加密后的私钥生成数字签名
|
||
var signingCredentials = new SigningCredentials(signingKey, signingAlogorithm);
|
||
//生成Token
|
||
var Token = new JwtSecurityToken(
|
||
issuer: _configuration["Authentication:Issuer"], //发布者
|
||
audience: _configuration["Authentication:Audience"], //接收者
|
||
claims: claimList, //存放的用户信息
|
||
notBefore: DateTime.UtcNow, //发布时间
|
||
expires: DateTime.UtcNow.AddMinutes(30), //有效期设置为1天
|
||
signingCredentials //数字签名
|
||
);
|
||
//生成字符串token
|
||
var TokenStr = new JwtSecurityTokenHandler().WriteToken(Token);
|
||
return TokenStr;
|
||
}
|
||
// 获取Token Payload信息
|
||
public T GetToken<T>(string token)
|
||
{
|
||
Type t = typeof(T);
|
||
object obj = Activator.CreateInstance(t);
|
||
var b = _jwtSecurityTokenHandler.ReadJwtToken(token);
|
||
foreach (var item in b.Claims)
|
||
{
|
||
PropertyInfo propertyInfo = t.GetProperty(item.Type);
|
||
if (propertyInfo != null && propertyInfo.CanRead)
|
||
{
|
||
propertyInfo.SetValue(obj, item.Value, null);
|
||
}
|
||
}
|
||
return (T)obj;
|
||
}
|
||
// 根据类生成Token 断言列表
|
||
private List<Claim> CreateClaimList<T>(T authUser)
|
||
{
|
||
var Class = typeof(T);
|
||
List<Claim> claimList = new();
|
||
foreach (var item in Class.GetProperties())
|
||
{
|
||
// 不将PassWord放入Token中
|
||
if (item.Name == "PassWord")
|
||
{
|
||
continue;
|
||
}
|
||
// 将UserName属性名重命名为username存入Token中
|
||
if (item.Name == "UserName")
|
||
{
|
||
claimList.Add(new Claim("username", Convert.ToString(item.GetValue(authUser))));
|
||
continue;
|
||
}
|
||
claimList.Add(new Claim(item.Name, Convert.ToString(item.GetValue(authUser))));
|
||
}
|
||
return claimList;
|
||
}
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
在登录方法中加入
|
||
|
||
```c#
|
||
public ViewUser Login(string UserName, string PassWord)
|
||
{
|
||
var res = _sqlServerDbContext.Users.Include(user => user.Role).FirstOrDefault(x => x.UserName == UserName);
|
||
if (res != null)
|
||
{
|
||
if (res.PassWord == Md5Encoding(PassWord))
|
||
{
|
||
// 生成JWT
|
||
var TokenStr = _tokenHelper.CreateJwtToken(res);
|
||
var config = new MapperConfiguration(cfg => cfg.CreateMap<User, ViewUser>()
|
||
.ForMember(d => d.username, opt => opt.MapFrom(src => src.UserName))
|
||
.AfterMap((src, des) => des.Roles = new Role[1] { src.Role })
|
||
.AfterMap((src, des) => des.Token = "bearer " + TokenStr) // 需要加上bearer
|
||
.AfterMap((src, des) => des.HomePath = "/dashboard/analysis")
|
||
.AfterMap((src, des) => des.password = null));
|
||
var mapper = config.CreateMapper();
|
||
return mapper.Map<ViewUser>(res);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
```
|
||
|
||
WebAPI 需要认证的加上`[Authorize]`注解,注意登录不能加
|
||
|
||
```c#
|
||
[AuthFilter]
|
||
[HttpPost(Name = "login")]
|
||
public ResultUtil Login(ViewUser viewUser) =>
|
||
ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password));
|
||
// 需要认证的API
|
||
[Authorize]
|
||
[AuthFilter]
|
||
[HttpGet(Name = "getUserInfo")]
|
||
public ResultUtil GetUserInfo()
|
||
{
|
||
Token = HttpContext.Request.Headers["Authorization"];
|
||
Token = Token.Split(" ")[1];
|
||
TokenHelper.Token = Token;
|
||
ViewUser us = _tokenHelper.GetToken<ViewUser>(Token);
|
||
return ResultUtil.ok(us);
|
||
}
|
||
```
|
||
|
||
访问登录接口
|
||
|
||

|
||
|
||
访问需要认证的接口,需要把Token放在请求头中,如果不携带Token,访问则报401
|
||
|
||

|
||
|
||
请求头Key 为 Authorization
|
||
|
||
访问成功
|
||
|
||

|
||
|
||
## 面向切面编程(AOP)
|
||
|
||
三大拦截器
|
||
|
||
`AuthorizeAttribute`
|
||
|
||
认证拦截器
|
||
|
||
`ActionFilterAttribute`
|
||
|
||
方法拦截器
|
||
|
||
```c#
|
||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||
using Microsoft.AspNetCore.Mvc.Filters;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Linq;
|
||
using System.IdentityModel.Tokens.Jwt;
|
||
using Castle.Core.Internal;
|
||
using XiaodaERP.Models;
|
||
using XiaodaERP.Utils;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Options;
|
||
|
||
namespace XiaodaERP.Attributes
|
||
{
|
||
public class AuthFilter : ActionFilterAttribute
|
||
{
|
||
//private readonly TokenHelper _tokenHelper;
|
||
//public AuthFilter(TokenHelper tokenHelper)
|
||
//{
|
||
// this._tokenHelper = tokenHelper;
|
||
//}
|
||
private readonly SqlServerDbContext _sqlServerDbContext;
|
||
public AuthFilter(SqlServerDbContext sqlServerDbContext)
|
||
{
|
||
this._sqlServerDbContext = sqlServerDbContext;
|
||
}
|
||
|
||
private SysActionLog sysActionLog = new()
|
||
{
|
||
ActionId = Guid.NewGuid().ToString().Replace("-", "").ToUpper()
|
||
};
|
||
public override void OnActionExecuting(ActionExecutingContext context)
|
||
{
|
||
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
|
||
string param = string.Empty;
|
||
string globalParam = string.Empty;
|
||
|
||
var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
|
||
foreach (var arg in context.ActionArguments)
|
||
{
|
||
string value = Newtonsoft.Json.JsonConvert.SerializeObject(arg.Value, Formatting.None, jsonSetting);
|
||
param += $"{arg.Key} : {value} \r\n";
|
||
globalParam += value;
|
||
}
|
||
// 方法名
|
||
Console.WriteLine(descriptor.ActionName);
|
||
// 参数值拼接
|
||
Console.WriteLine(globalParam);
|
||
// 参数名 与 值
|
||
Console.WriteLine(param);
|
||
sysActionLog.ActionName = descriptor.ActionName;
|
||
sysActionLog.RequestParams = param;
|
||
}
|
||
|
||
public override void OnActionExecuted(ActionExecutedContext context)
|
||
{
|
||
// 获取请求Host
|
||
Console.WriteLine(context.HttpContext.Request.Host);
|
||
sysActionLog.RequestHost = context.HttpContext.Request.Host.ToString();
|
||
// 获取请求方法
|
||
Console.WriteLine(context.HttpContext.Request.Method);
|
||
sysActionLog.RequestMethod = context.HttpContext.Request.Method;
|
||
// 获取请求Url
|
||
Console.WriteLine(context.HttpContext.Request.Path);
|
||
sysActionLog.RequestPath = context.HttpContext.Request.Path.ToString();
|
||
// 获取应答返回状态码
|
||
Console.WriteLine(context.HttpContext.Response.StatusCode);
|
||
if (context.HttpContext.Request.Path.Equals("/api/User/login"))
|
||
{
|
||
sysActionLog.ActionTime = DateTime.Now;
|
||
}
|
||
else
|
||
{
|
||
string Token = context.HttpContext.Request.Headers["Authorization"];
|
||
// Token失效
|
||
if (Token.IsNullOrEmpty())
|
||
{
|
||
|
||
}
|
||
else
|
||
{
|
||
Token = Token.Split(" ")[1];
|
||
TokenHelper.Token = Token;
|
||
ViewUser us = new TokenHelper(new JwtSecurityTokenHandler()).GetToken<ViewUser>(Token);
|
||
Console.WriteLine(us.UserId);
|
||
sysActionLog.ActionUserId = us.UserId;
|
||
Console.WriteLine(us.username);
|
||
sysActionLog.ActionUserName = us.username;
|
||
}
|
||
sysActionLog.ActionTime = DateTime.Now;
|
||
}
|
||
_sqlServerDbContext.SysActionLogs.Add(sysActionLog);
|
||
_sqlServerDbContext.SaveChanges();
|
||
}
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
接口上使用
|
||
|
||
```c#
|
||
//[AuthFilter]
|
||
[TypeFilter(typeof(AuthFilter))]
|
||
[HttpPost(Name = "login")]
|
||
public ResultUtil Login(ViewUser viewUser) =>
|
||
ResultUtil.ok(_userService.Login(viewUser.username, viewUser.password));
|
||
|
||
[Authorize]
|
||
//[AuthFilter] // 注解为拦截器类名
|
||
[TypeFilter(typeof(AuthFilter))] // 因为主键中使用了构造器依赖注入,所以需要使用TypeFilter,并需要在顶级语句中注入 AuthFilter
|
||
[HttpGet(Name = "getUserInfo")]
|
||
public ResultUtil GetUserInfo()
|
||
{
|
||
Token = HttpContext.Request.Headers["Authorization"];
|
||
Token = Token.Split(" ")[1];
|
||
TokenHelper.Token = Token;
|
||
ViewUser us = _tokenHelper.GetToken<ViewUser>(Token);
|
||
return ResultUtil.ok(us);
|
||
}
|
||
```
|
||
|
||
顶级语句中注入
|
||
|
||
```c#
|
||
builder.Services.AddScoped<AuthFilter>();
|
||
```
|
||
|
||
|
||
|
||
`ExceptionFilterAttribute`
|
||
|
||
异常拦截器
|