2022-12-07 14:23:24 +08:00

485 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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支持
![image-20221121144928205](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221121144928205.png)
### 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包
![image-20221130161234399](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161234399.png)
![image-20221130161319595](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221130161319595.png)
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`
![image-20221206130808039](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206130808039.png)
`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);
}
```
访问登录接口
![image-20221206131847333](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206131847333.png)
访问需要认证的接口需要把Token放在请求头中如果不携带Token访问则报401
![image-20221206132042533](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132042533.png)
请求头Key 为 Authorization
访问成功
![image-20221206132124913](https://markdownhexo.oss-cn-hangzhou.aliyuncs.com/img/image-20221206132124913.png)
## 面向切面编程(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`
异常拦截器