首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(下)

《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(下)

作者头像
郑子铭
发布于 2021-01-13 07:46:25
发布于 2021-01-13 07:46:25
55400
代码可运行
举报
运行总次数:0
代码可运行

第 8 章 认证和安全

8.3 HTTPS

HTTP 协议能够在客户端和服务器之间传递信息,特点是以明文的方式发送内容,并不提供任何方式的数据加密

为了解决 HTTP 协议这一缺陷,需要使用另一种协议:HTTPS,它在 HTTP 的基础上加入了安全套接层 SSL 协议

SSL 层依靠证书来验证服务器的身份,并在传输层为浏览器和服务器之间的通信加密

自 ASP.NET Core 2.1 起,在默认情况下,所创建的 ASP.NET Core 应用程序都启用了 HTTPS

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    
    。。。
}

在 launchSettings.json 配置文件中也包含了 HTTPS 端口配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"sslPort": 44304

"applicationUrl": "https://localhost:5001;http://localhost:5000",

HTTPS 重定向中间件会将所有的非安全请求重定向到安全的 HTTPS 协议上,它使用 HttpsRedirectionOptions 对象中的配置来进行重定向

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace Microsoft.AspNetCore.HttpsPolicy
{
  public class HttpsRedirectionOptions
  {
    public int RedirectStatusCode { get; set; } = 307;// 用于设置重定向时的状态码,默认值307 Temporary Redirect

    public int? HttpsPort { get; set; }// 重定向URL中要用到的端口号
  }
}

若要修改重定向选项,则可以在 ConfigureServices 方法中添加如下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
services.AddHttpsRedirection(option =>
    {
        option.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
        option.HttpsPort = 5001;
    }
);

HSTS 中间件使用 HSTS 来进一步保证客户端和服务器之间数据传输的安全,作用是强制客户端使用 HTTPS 与服务器建立链接,实现方式是在响应消息中添加 Strict-Transport-Security 消息头,该消息头可以使浏览器在接下来指定的时间内,强制当前域名只能通过 HTTPS 进行访问

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 类型对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace Microsoft.AspNetCore.DataProtection
{
  public interface IDataProtectionProvider
  {
    IDataProtector CreateProtector(string purpose);
  }
}

IDataProtector 接口用于执行实际的数据保护操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
namespace Microsoft.AspNetCore.DataProtection
{
  public interface IDataProtector : IDataProtectionProvider
  {
    byte[] Protect(byte[] plaintext);

    byte[] Unprotect(byte[] protectedData);
  }
}

为了方便使用上述两个接口,在相同的命名空间中还包含了为它们定义的扩展方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,需要先添加服务

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
services.AddDataProtection();

之后,在需要的位置,将 IDataProtectionProvider 接口注入即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var protectorA = DataProtectionProvider.CreateProtector("A");
var protectorB = protectorA.CreateProtector("B");
var protectorC = protectorB.CreateProtector("C");

需要注意的是,在对数据解密时,必须使用与加密时相同的方式创建的 IDataProtector 对象

为了更方便地创建具有层次的 IDataProtector 对象,可以使用如下 IDataProtectionProvider 接口的扩展方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DataProtectionProvider.CreateProtector("Parent", "Child");

如果使用上述 protectorC 对象加密信息,则可以使用如下方式进行解密

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var content = protectorC.Protect("Hello");
var protector = DataProtectionProvider.CreateProtector("A", "B", "C");
var rawContent = protector.Unprotect(content);

使用 protectorC 加密的内容,可以使用 CreateProtector("A", "B", "C") 创建的 IDataProtector 进行解密。这种具有层次的 IDataProtector 在根据不同版本或不同用户保护数据时非常方便

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var protectV1 = DataProtectionProvider.CreateProtector("DemoApp.ValueController", "v1");
var protectV2 = DataProtectionProvider.CreateProtector("DemoApp.ValueController", "v2");

为数据加密设置有效时间,在 Microsoft.AspNetCore.DataProtection 包中为 IDataProtector 接口定义了一个扩展方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 接口定义如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的使用方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 接口的一个实现,它的加密和解密功能具有“一次性”的特点,当密文不需要持久化时,可以使用这种方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 异常

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
sort命令
Linux sort 命令用于将文本文件内容加以排序,可针对文本文件的内容,以行为单位来排序(默认以ASCII编码作比较)。
cultureSun
2023/05/18
6700
Linux命令(6)——sort命令
以行为单位对文本文件的内容进行排序,将结果显示在标准输出,比较原则是从行首字符向后,依次按ASCII码值进行比较,最后按升序输出。如果file参数指定多个文件,那么 sort 命令将这些文件纵向连接起来,当作一个文件进行排序。
恋喵大鲤鱼
2018/08/03
2.7K0
sort命令
sort命令 sort命令用于将文本文件内容加以排序,可针对文本文件的内容,以行为单位来排序。 语法 sort [OPTION]... [FILE]... sort [OPTION]... --files0-from=F 参数 -b, --ignore-leading-blanks: 忽略前导空格。 -d, --dictionary-order: 只考虑空格和字母数字字符。 -f, --ignore-case: 将小写字符转为大写字符。 -g, --general-numeric-sort: 按一般数值进行
WindRunnerMax
2021/01/08
6440
LinuxShell命令sort
sort 命令用于对给定的文件中的行进行排序并写到标准输出上。如果没有给定文件或者给定的文件名为 - ,则从标准输入读取数据。
hotarugali
2022/02/28
5730
Linux sort命令简介
用sort对文件排序,发现这个命令比想象中要复杂和强大,仔细研究了一下文档,记录一下。
猿哥
2019/06/20
2.5K0
【linux命令讲解大全】082.Linux命令大全:apt-sortpkgs和sort详解及使用示例
apt-sortpkgs命令是Debian Linux下对软件包索引文件进行排序的简单工具。
全栈若城
2024/03/02
1450
Linux-sort排序
sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出。sort命令既可以从特定的文件,也可以从stdin中获取输入。
小小工匠
2021/08/16
2.8K0
shell五分钟系列之sort
写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率
早起的鸟儿有虫吃
2023/03/28
4300
shell五分钟系列之sort
Linux 之 sort 命令
sort 是用来排序的,Unix Shell 的传统是对问本行做处理,因此 sort 也是对文本行进行排序,如果需要排序字段,则可以通过指定 -k,-t 等选项来实现。
Dylan Liu
2020/05/26
2.6K0
9.Linux文件管理命令---sort按顺序显示文件内容
不能将输出直接发送到输入文件,因为这会破坏输入文件。这就是为何需要将它发送到临时文件中,然后将该文件重命名为/etc/passwd 的原因。如果想倒转排序的次序,则应当使用-r 选项。还可以用-u 选项来禁止打印相同的行。
度假的小鱼
2025/01/02
3820
9.Linux文件管理命令---sort按顺序显示文件内容
几条命令找出服务器上的垃圾文件
Linux用久了也可能出现很多垃圾文件,下面跟着老高用几行命令揪出来占用系统空间的家伙们!
老高的技术博客
2022/12/28
7290
uniq、sort命令理解
ps:发现和原文件相比,只是把相邻的内容去重; 如果要把所有的去除,就要用到 sort,sort 是排序的命令,让重复的行相邻
cuijianzhe
2022/06/14
9590
Natural Sort: How to sort file names naturally
When a programmer is given the task of sorting file names in a list, it might be tempting to sort using something like std::sort(). The problem with that is: std::sort() sorts alphabetically. Suppose we have a list of file like this:
Miigon
2022/10/27
5630
Linux 命令 | 每日一学,文本处理之内容分割排序实践
描述:用来显示行中的指定部分,删除文件中指定字段,在文件的每一行中提取片断, 在每个文件 FILE 的各行中, 把提取的片断显示在标准输出。
全栈工程师修炼指南
2024/09/24
2830
Linux 命令 | 每日一学,文本处理之内容分割排序实践
《Linux命令行与shell脚本编程大全》 第四章
4.1 监测程序 1. ps  默认只显示运行在当前控制台下的属于当前用户的进程。  可以接很多选项,比如 -A表示所有进程  -e等。 2. ps -l  查看进程更多信息 UID:启动这些进程的用户 PID:进程的进程ID PPID:父进程的进程ID C:进程生命周期中的CPU利用率 TTY:进程启动时的终端设备 TIME:运行进程需要的累计CPU时间 CMD:启动的程序名称 PRI:进程的优先级(数字越大代表越低的优先级) ADDR:进程的内存地址 F:内核分配给进程的系统标记 S:进程的状态(O正在
xcywt
2018/01/11
1.1K0
《Linux命令行与shell脚本编程大全》 第四章
运维分享|Linux指令之文本编辑工具cat和more
简介: cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上。cat命令用于查看内容较少的纯文本文件。使用工具查看文本文件,让我们快速响应。
六月暴雪飞梨花
2023/11/27
3530
运维分享|Linux指令之文本编辑工具cat和more
在线matlab代码学习神器Octave Online
Octave与MATLAB完全兼容,免安装使用方便。注册和非注册功能会有不同,如下:
zhangrelay
2019/01/23
1.5K0
第二十一章 : 文本处理
All Unix-like operating systems rely heavily on text files for several types of datastorage. So it makes sense that there are many tools for manipulating text. In thischapter, we will look at programs that are used to “slice and dice” text. In the nextchapter, we will look at more text processing, focusing on programs that are used toformat text for printing and other kinds of human consumption.
砖业洋__
2023/05/06
6570
提升开发效率N倍的20+命令行神器!(附 demo)
读者福利:点这里送几本我们部门出的新书——《弹性计算:无处不在的算力》,免费包邮到家,欢迎大家来抽奖,也帮忙 review 下抽奖的代码。
程序猿石头
2020/09/10
1.1K0
提升开发效率N倍的20+命令行神器!(附 demo)
【小码匠自习室】让错误成为孩子进步的阶梯
碎碎念 今天梳理了这篇文章,同一个地方只能跌一次跟头,再重复错误肯定被老码农敲脑袋 梳理这篇文章源于3月份参加NOI Online测试赛没有全文比较输出文件内容,只是对比了几个值,导致爆零( ̄﹏ ̄;) 标题是老码农起的,心灵鸡汤太多了,以后想管他叫”唐鸡汤“了。(*^_^*) 准备测试文件 mode_ex1.ans 4 1 5 1 4 2 4 8 2 1 2 3 4 5 mode_ex1-2.ans 修改了第13行数据:4 -> 6 4 1 5 1 4 2 4 8 2 1 2 3 6 5 mode_ex
小码匠
2022/08/08
3980
相关推荐
sort命令
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档