第 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章)-- 读书笔记(下)的更多相关文章

  1. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  2. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  3. 菜单Menu(AS开发实战第四章学习笔记)

    4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...

  4. [Android]《Android艺术开发探索》第一章读书笔记

    1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...

  5. 温故知新,使用ASP.NET Core创建Web API,永远第一次

    ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...

  6. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  7. 零基础ASP.NET Core WebAPI团队协作开发

    零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...

  8. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

  9. 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战

    大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...

  10. 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 ...

随机推荐

  1. C#查找算法2:插值查找

    插值查找,有序表的一种查找方式.插值查找是根据查找关键字与查找表中最大最小记录关键字比较后的查找方法.插值查找基于二分查找,将查找点的选择改进为自适应选择,提高查找效率. 原理:    (midInd ...

  2. docker 原理之 user namespace(下)

    1. user namespace user namespace 主要隔离了安全相关的标识符和属性,包括用户 ID,用户组 ID,key 和 capabilities 等.同样一个用户 id 在不同 ...

  3. The requested URL could not be retrieved

    在开发过程中,调用对外接口,返回了一长串的标签提示,如下 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "ht ...

  4. 当ChatGPT遇上了CoT

    最近在看CoT(Chain-of-Thought,思维链)方面的论文<Chain-of-Thought Prompting Elicits Reasoning in Large Language ...

  5. Verilog Review

    Agenda 目的 Verilog概述 Verilog建模 模块 模块组成 书写建议 时延 Verilog基本语法 标识符 可读性 注释 空格 数据类型 操作数 运算符 条件语句 循环语句 函数 Ve ...

  6. C++中不支持strdup(),使用_strdup()

    1.问题 C4996 'strdup': The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conf ...

  7. ingress nginx 支持的K8S版本以及nginx版本信息

  8. 一个监控数据的思考-sockets_used

    一个监控数据的思考-sockets_used 背景 最近跟踪一个项目问题. Grafana的监控了里面有一个tcp的使用监控 CurrEstab 的数据量是: 700-2000 左右 但是同时有一个非 ...

  9. [转帖]GCC 编译及编译选项

    俗话说:'工欲善其事,必先利其器',一直在工作中使用GNU C编译器(以下简称GCC),这里对GCC的一些警告选项细致的分析,并列举几个简单的例子[注1]供分析参考. 1. -Wall集合警告选项我们 ...

  10. Nginx与Tomcat作为前端服务器的性能比较

    Nginx与Tomcat作为前端服务器的性能比较 摘要 最近总遇到使用tomcat还是使用nginx进行前端文件访问的争论 想着出差周末在酒店, 可以自己进行一下简单的测试. 希望能够对未来的工作进行 ...