《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(下)
第 8 章 认证和安全
8.3 HTTPS
HTTP 协议能够在客户端和服务器之间传递信息,特点是以明文的方式发送内容,并不提供任何方式的数据加密
为了解决 HTTP 协议这一缺陷,需要使用另一种协议:HTTPS,它在 HTTP 的基础上加入了安全套接层 SSL 协议
SSL 层依靠证书来验证服务器的身份,并在传输层为浏览器和服务器之间的通信加密
自 ASP.NET Core 2.1 起,在默认情况下,所创建的 ASP.NET Core 应用程序都启用了 HTTPS
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
。。。
}
在 launchSettings.json 配置文件中也包含了 HTTPS 端口配置
"sslPort": 44304
"applicationUrl": "https://localhost:5001;http://localhost:5000",
HTTPS 重定向中间件会将所有的非安全请求重定向到安全的 HTTPS 协议上,它使用 HttpsRedirectionOptions 对象中的配置来进行重定向
namespace Microsoft.AspNetCore.HttpsPolicy
{
public class HttpsRedirectionOptions
{
public int RedirectStatusCode { get; set; } = 307;// 用于设置重定向时的状态码,默认值307 Temporary Redirect
public int? HttpsPort { get; set; }// 重定向URL中要用到的端口号
}
}
若要修改重定向选项,则可以在 ConfigureServices 方法中添加如下代码
services.AddHttpsRedirection(option =>
{
option.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
option.HttpsPort = 5001;
}
);
HSTS 中间件使用 HSTS 来进一步保证客户端和服务器之间数据传输的安全,作用是强制客户端使用 HTTPS 与服务器建立链接,实现方式是在响应消息中添加 Strict-Transport-Security 消息头,该消息头可以使浏览器在接下来指定的时间内,强制当前域名只能通过 HTTPS 进行访问
services.AddHsts(options =>
{
options.IncludeSubDomains = true;// 表明该网站所有子域名也必须通过HTTPS协议来访问
options.Preload = true;// 可选参数,只有在申请将当前网站的域名加入浏览器内置列表时,才需要使用它
options.MaxAge = TimeSpan.FromDays(120);// 指定时间内,这个网站必须通过HTTPS协议来访问
options.ExcludedHosts.Clear();// 由于本地服务器不会使用HTTPS,为了查看效果,需要清除所有被排除的主机列表
});
之所以应该在正式环境中使用 HSTS,是因为 HSTS 配置会被浏览器缓存,因此不建议在开发环境中使用 HSTS
8.4 数据保护
Web 应用程序通常需要存储安全敏感数据,ASP.NET Core 提供了数据保护 API,用于加密和解密数据功能
数据保护 API 主要包含两个接口:IDataProtectionProvider 与 IDataProtector
IDataProtectionProvider 接口主要用于创建 IDataProtector 类型对象
namespace Microsoft.AspNetCore.DataProtection
{
public interface IDataProtectionProvider
{
IDataProtector CreateProtector(string purpose);
}
}
IDataProtector 接口用于执行实际的数据保护操作
namespace Microsoft.AspNetCore.DataProtection
{
public interface IDataProtector : IDataProtectionProvider
{
byte[] Protect(byte[] plaintext);
byte[] Unprotect(byte[] protectedData);
}
}
为了方便使用上述两个接口,在相同的命名空间中还包含了为它们定义的扩展方法
namespace Microsoft.AspNetCore.DataProtection
{
public static class DataProtectionCommonExtensions
{
public static IDataProtector CreateProtector(
this IDataProtectionProvider provider,
IEnumerable<string> purposes)
{
if (provider == null)
throw new ArgumentNullException(nameof (provider));
if (purposes == null)
throw new ArgumentNullException(nameof (purposes));
bool flag = true;
IDataProtectionProvider protectionProvider = provider;
foreach (string purpose in purposes)
{
if (purpose == null)
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof (purposes));
protectionProvider = (IDataProtectionProvider) (protectionProvider.CreateProtector(purpose) ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null."));
flag = false;
}
if (flag)
throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof (purposes));
return (IDataProtector) protectionProvider;
}
public static IDataProtector CreateProtector(
this IDataProtectionProvider provider,
string purpose,
params string[] subPurposes)
{
if (provider == null)
throw new ArgumentNullException(nameof (provider));
if (purpose == null)
throw new ArgumentNullException(nameof (purpose));
IDataProtector provider1 = provider.CreateProtector(purpose);
if (subPurposes != null && subPurposes.Length != 0)
provider1 = provider1 != null ? provider1.CreateProtector((IEnumerable<string>) subPurposes) : (IDataProtector) null;
return provider1 ?? CryptoUtil.Fail<IDataProtector>("CreateProtector returned null.");
}
public static IDataProtectionProvider GetDataProtectionProvider(
this IServiceProvider services)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
IDataProtectionProvider service = (IDataProtectionProvider) services.GetService(typeof (IDataProtectionProvider));
if (service != null)
return service;
throw new InvalidOperationException(Resources.FormatDataProtectionExtensions_NoService((object) typeof (IDataProtectionProvider).FullName));
}
public static IDataProtector GetDataProtector(
this IServiceProvider services,
IEnumerable<string> purposes)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
if (purposes == null)
throw new ArgumentNullException(nameof (purposes));
return services.GetDataProtectionProvider().CreateProtector(purposes);
}
public static IDataProtector GetDataProtector(
this IServiceProvider services,
string purpose,
params string[] subPurposes)
{
if (services == null)
throw new ArgumentNullException(nameof (services));
if (purpose == null)
throw new ArgumentNullException(nameof (purpose));
return services.GetDataProtectionProvider().CreateProtector(purpose, subPurposes);
}
public static string Protect(this IDataProtector protector, string plaintext)
{
if (protector == null)
throw new ArgumentNullException(nameof (protector));
if (plaintext == null)
throw new ArgumentNullException(nameof (plaintext));
try
{
byte[] bytes = EncodingUtil.SecureUtf8Encoding.GetBytes(plaintext);
return WebEncoders.Base64UrlEncode(protector.Protect(bytes));
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
throw Error.CryptCommon_GenericError(ex);
}
}
public static string Unprotect(this IDataProtector protector, string protectedData)
{
if (protector == null)
throw new ArgumentNullException(nameof (protector));
if (protectedData == null)
throw new ArgumentNullException(nameof (protectedData));
try
{
byte[] protectedData1 = WebEncoders.Base64UrlDecode(protectedData);
byte[] bytes = protector.Unprotect(protectedData1);
return EncodingUtil.SecureUtf8Encoding.GetString(bytes);
}
catch (Exception ex) when (ex.RequiresHomogenization())
{
throw Error.CryptCommon_GenericError(ex);
}
}
}
}
前两个方法用于根据多个目的的字符串来创建 IDataProtector,后两个方法使用 IDataProtector 的 Protect 和 Unprotect 方法能够接受并返回字符串
要在程序中使用数据保护 API,需要先添加服务
services.AddDataProtection();
之后,在需要的位置,将 IDataProtectionProvider 接口注入即可
namespace WebApplication1.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValueController : Controller
{
private List<Student> students = new List<Student>();
public IDataProtectionProvider DataProtectionProvider { get; set; }
public ValueController(IDataProtectionProvider dataProtectionProvider)
{
DataProtectionProvider = dataProtectionProvider;
students.Add(new Student
{
Id = "1",
Name = "Jim"
});
}
[HttpGet]
public ActionResult<IEnumerable<Student>> Get()
{
var protector = DataProtectionProvider.CreateProtector("ProtectResourceId");
var result = students.Select(s => new Student
{
Id = protector.Protect(s.Id),// 加密
Name = s.Name
});
return result.ToList();
}
[HttpGet]
public ActionResult<Student> Get(string id)
{
var protector = DataProtectionProvider.CreateProtector("ProtectResourceId");
var rawId = protector.Unprotect(id);// 解密
var targetItem = students.FirstOrDefault(s => s.Id == rawId);
return new Student {Id = id, Name = targetItem.Name};
}
}
public class Student
{
public string Id { get; set; }
public string Name { get; set; }
}
}
由于 IDataProtector 接口同样可同于创建 IDataProtector 对象,因此可以创建具有层次的 IDataProtector 对象
var protectorA = DataProtectionProvider.CreateProtector("A");
var protectorB = protectorA.CreateProtector("B");
var protectorC = protectorB.CreateProtector("C");
需要注意的是,在对数据解密时,必须使用与加密时相同的方式创建的 IDataProtector 对象
为了更方便地创建具有层次的 IDataProtector 对象,可以使用如下 IDataProtectionProvider 接口的扩展方法
DataProtectionProvider.CreateProtector("Parent", "Child");
如果使用上述 protectorC 对象加密信息,则可以使用如下方式进行解密
var content = protectorC.Protect("Hello");
var protector = DataProtectionProvider.CreateProtector("A", "B", "C");
var rawContent = protector.Unprotect(content);
使用 protectorC 加密的内容,可以使用 CreateProtector("A", "B", "C") 创建的 IDataProtector 进行解密。这种具有层次的 IDataProtector 在根据不同版本或不同用户保护数据时非常方便
var protectV1 = DataProtectionProvider.CreateProtector("DemoApp.ValueController", "v1");
var protectV2 = DataProtectionProvider.CreateProtector("DemoApp.ValueController", "v2");
为数据加密设置有效时间,在 Microsoft.AspNetCore.DataProtection 包中为 IDataProtector 接口定义了一个扩展方法
public static ITimeLimitedDataProtector ToTimeLimitedDataProtector(
this IDataProtector protector)
{
if (protector == null)
throw new ArgumentNullException(nameof (protector));
return protector is ITimeLimitedDataProtector limitedDataProtector ? limitedDataProtector : (ITimeLimitedDataProtector) new TimeLimitedDataProtector(protector);
}
该方法能够将 IDataProtector 对象转换为 ITimeLimitedDataProtector 类型的对象,为密文增加有效时间
ITimeLimitedDataProtector 接口定义如下
namespace Microsoft.AspNetCore.DataProtection
{
public interface ITimeLimitedDataProtector : IDataProtector, IDataProtectionProvider
{
ITimeLimitedDataProtector CreateProtector(string purpose);
byte[] Protect(byte[] plaintext, DateTimeOffset expiration);
byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration);
}
}
DateTimeOffset 类型参数表示有效期
以下示例展示了 ITimeLimitedDataProtector 的使用方法
var protector = DataProtectionProvider.CreateProtector("testing").ToTimeLimitedDataProtector();
var content = protector.Protect("Hello", DateTimeOffset.Now.AddMinutes(10));
// 等待一段时间
try
{
var rawContent = protector.Unprotect(content, out DateTimeOffset expiration);
}
catch (CryptographicException ex)
{
Logger.logError(ex.Message, ex);
}
Microsoft.AspNetCore.DataProtection 包中还提供了 EphemeralDataProtectionProvider 类,作为 IDataProtectionProvider 接口的一个实现,它的加密和解密功能具有“一次性”的特点,当密文不需要持久化时,可以使用这种方式
private void EphemeralDataProtectionTest()
{
const string Purpose = "DemoPurpose";
EphemeralDataProtectionProvider provider = new EphemeralDataProtectionProvider();
var protector = provider.CreateProtector(Purpose);
var content = protector.Protect("Hello");
var rawContent = protector.Unprotect(content);
EphemeralDataProtectionProvider provider2 = new EphemeralDataProtectionProvider();
var protector2 = provider2.CreateProtector(Purpose);
rawContent = protector2.Unprotect(content);// 这里会出现异常
}
对于第二个 EphemeralDataProtectionProvider 尽管创建了 IDataProtector 时,使用了相同的字符串,但由于是不同的实例,因此尝试解密第一个对象加密的内容时,将会出错,抛出 CryptographicException 异常
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。
《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(下)的更多相关文章
- 使用ASP.NET Core构建RESTful API的技术指南
译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...
- 4类Storage方案(AS开发实战第四章学习笔记)
4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...
- 菜单Menu(AS开发实战第四章学习笔记)
4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...
- [Android]《Android艺术开发探索》第一章读书笔记
1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...
- 温故知新,使用ASP.NET Core创建Web API,永远第一次
ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...
- 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践
本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...
- 零基础ASP.NET Core WebAPI团队协作开发
零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...
- ASP.NET Core WebApi构建API接口服务实战演练
一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...
- 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战
大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...
- Asp.Net Core 5 REST API - Step by Step
翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...
随机推荐
- Git | git branch 分支操作
假设我们已经有了稳定的代码,现在我想整一些花活.比较安全的一个方式是,在新的分支上整活. 新建 vga 分支:git branch vga,然后切换到 vga 分支:git switch vga,或者 ...
- P5728 【深基5.例5】旗鼓相当的对手
1.题目介绍 2.题解 2.1 二维数组 思路 主要熟悉vector创建二维数组的方法 vector<vector> ans(N,vector(3)); 这里第一个元素表明数组大小,第二个 ...
- 【OpenVINO】基于 OpenVINO Python API 部署 RT-DETR 模型
目录 1. RT-DETR 2. OpenVINO 3. 环境配置 3.1 模型下载环境 3.2 模型部署环境 4. 模型下载与转换 4.1 PaddlePaddle模型下载 4.2 IR模型转换 5 ...
- [转帖]Shell脚本数组(实现冒泡排序,直接选择排序,反转排序)
目录 数组 数组定义方法 数组包括的数据类型 获取数组长度 读取某下标赋值 数组遍历 数组切片 数组替换 删除数组 追加数组中的元素 从函数返回数组 加法传参运算 乘法传参运算 数组排序算法 冒泡排序 ...
- [转帖]Megacli 错误码
MegaCLI Error Messages 0x00 Command completed successfully 0x01 Invalid command 0x02 DCMD opcode is ...
- [转帖]一个轻量的Linux运维监控脚本
https://zhuanlan.zhihu.com/p/472040635 写在前面 我的需求 嗯,有几台很老的机器,上面部署的几个很老的应用 我需要每周对机器上的一些内存,磁盘,线程,应用和数据库 ...
- 【转帖】3.JVM内存结构概述
目录 1.JVM内存结构 1.JVM内存结构 在JVM系列的第一篇文章中已经给出了JVM内存结构的简图,下面是JVM内存结构更加详细的图. 同样,JVM的内存结构可以分为上中下3层. 上层主要是类加载 ...
- [转帖]IO多路复用的三种机制Select,Poll,Epoll
I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作 ...
- Sysbench的简单学习-编译与安装
sysbench的简单学习-编译与安装 摘要 github上面获取一下最新版本 https://github.com/akopytov/sysbench 注意现在 2023.2.17 最新版是 sys ...
- 据说这道Go面试题90%的人都搞错了!
[Go面试向]defer与time.sleep初探 大家好,我是阳哥,这是我们Go就业训练营小伙伴 寸铁同学 整理的一道很有意思的面试题. 知其然更要知其所以然,通过断点调试的思路带你搞清楚来龙去脉. ...