首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
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
56500
代码可运行
举报
运行总次数: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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
3-了解.json文件
它的作用是配置小程序的页面,这个配置项是必填的,它接受一个数组,里面的每一项都是字符串,从上面给出的代码:
py3study
2020/01/10
8640
小程序当中的文件类型,组织结构,配置,知识点等
在微信小程序中有四种文件类型,主要分样式,骨架,业务,配置,样式为wxss,这里的wxss与页面的css相类似,骨架为wxml,这里的wxml于页面的html相类似,业务逻辑都是以js为结尾,配置文件为json结尾。
达达前端
2019/07/03
8510
小程序当中的文件类型,组织结构,配置,知识点等
「小程序JAVA实战」 小程序手写属于自己的第一个demo(六)
自己尝试的写一个小demo,用到自定义样式,自定义底部导航,页面之间的跳转等小功能。 官方文档对于小程序开发来说要时刻打开https://developers.weixin.qq.com/minipr
IT架构圈
2018/12/17
1.1K0
「小程序JAVA实战」 小程序手写属于自己的第一个demo(六)
微信小程序云开发基础知识扫盲篇(一)文档结构
这个是小程序的脚本代码文件,可以在这个文件上进行监听,并处理小程序的一些生命周期(比如一些全局变量)
用户3004405
2021/07/14
7310
微信小程序开发环境安装以及相关设置配置
软件安装地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
小小咸鱼YwY
2020/06/19
2.7K0
微信小程序开发环境安装以及相关设置配置
微信小程序,开发大起底
作者简介:张智超,北京微函工坊开发工程师,CSDN微信开发知识库特邀编辑。微信小程序爱好者。 感谢@翟东平 @qq_31383345 @nigelyq 等热情参与和共建 责编:CSDN知识图谱小助手 「小程序」是什么 张小龙在朋友圈里这样解释道:小程序是一种不需要下载安装即可使用的应用,它实现了应用「触手可及」的梦想,用户扫一扫或搜一下即可打开应用。也体现了「用完即走」的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。 微信小程序是一种全新的连接用户与服务的方式,它可以
智能算法
2018/04/03
1.6K0
微信小程序,开发大起底
小程序 Tip | 基础概述
每个子目录中保存着一个页面的相关文件 —— 通常是4种不同扩展名的文件, 分别是页面中的逻辑文件、页面结构文件、样式表文件、配置文件; 为了减少开发时的配置项,框架特别约定描述页面的这4个文件必须具备相同的路径和文件名;
凌川江雪
2022/01/20
1K0
小程序 Tip | 基础概述
微信小程序个人心得「建议收藏」
官方给出了app.json五个配置项列表.(pages(Array), window(Object), tabBar(Object), networkTimeout(Object), debug(Boolean)),接着我们就详细的分下一下这几个配置项.
全栈程序员站长
2022/11/04
2.1K0
微信小程序个人心得「建议收藏」
微信小程序-开发入门(一)
微信小程序已经火了一段时间了,之前一直也在关注,就这半年的发展来看,相对原生APP大部分公司还是不愿意将主营业务放到微信平台上,以免受制于腾讯,不过就小程序的应用场景(用完即走和二维码分发等)还是很值得我们学习的,技术上面如果了解React的话,会发现他们在组件化上面有很多雷同之处。说白了,小程序就是基于微信平台的H5轻应用,微信将系统底层功能(设备、位置、媒体、文件等)和微信自身功能(登录、支付、分享等)封装成相应API供小程序调用。 自己根据官方文档写过一个DOME,借助和风天气开放API接口,实现天气
小古哥
2018/03/08
2.6K2
微信小程序-开发入门(一)
微信小程序初步入坑指南
https://developers.weixin.qq.com/miniprogram/dev/devtools/beta.html
mySoul
2018/08/27
1.4K0
微信小程序开发基础
查看官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/
达达前端
2019/07/03
4K0
微信小程序开发基础
全栈开发工程师微信小程序-中(中)
official-account 用户扫码打开小程序,在小程序内配置公众号关注组件,快捷关注公众号.
达达前端
2019/07/03
9020
全栈开发工程师微信小程序-中(中)
【愚公系列】《微信小程序与云开发从入门到实践》005-小程序项目的基本结构
在当今移动互联网的迅猛发展中,小程序以其轻便、高效的特点,逐渐成为了吸引用户的重要工具。无论是电商、社交还是生活服务,各类小程序如雨后春笋般涌现,改变了我们与应用的交互方式。然而,很多开发者在初次接触小程序时,往往对其项目结构感到迷茫。了解小程序的基本结构不仅是开发的第一步,更是构建高效、可维护应用的基础。
愚公搬代码
2025/01/10
2730
微信小程序入门《四》实例:导航栏样式、tabBar导航栏
实例内容 导航栏样式设置 tabBar导航栏 实例一:导航栏样式设置 小程序的导航栏样式在app.json中定义。 这里设置导航,背景黑色,文字白色,文字内容测试小程序 app.json内容: { "pages":[ "pages/index/index", "pages/login/login", "pages/logs/logs" ], "window":{ "backgroundTextStyle":"red", "navigationBarBack
极乐君
2018/02/05
3.6K0
微信小程序入门《四》实例:导航栏样式、tabBar导航栏
微信小程序开发(全局配置文件)
微信小程序中全局配置文件指:app.json文件,该文件通过微信开发者工具新建项目后会自动生成,无需自己手动创建。
全栈开发日记
2022/05/13
1.1K0
微信小程序开发(全局配置文件)
微信小程序基础
查看官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/
达达前端
2019/07/03
1.5K0
微信小程序基础
微信小程序 开发笔记
参考:https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html
yiyun
2022/04/01
5680
微信小程序 开发笔记
【微信小程序】从入门到放弃
前言 关于微信小程序是什么,能做什么的问题,草民在此不在罗列了,随着小程序的天天刷屏,想必您也是来吃一些干货,本篇博文和大家走进微信小程序的从入门到放弃~ 微信小程序开放功能 草民看过很多的文档,微信
极乐君
2018/02/05
1.9K0
【微信小程序】从入门到放弃
02-微信小程序目录结构及配置
微信目录结构配置说明app.json 配置项window配置项restartStrategytabBar配置项创建一个自己的页面真机调试
度假的小鱼
2023/11/20
9890
02-微信小程序目录结构及配置
微信小程序-开发入门(一)
微信小程序已经火了一段时间了,之前一直也在关注,就这半年的发展来看,相对原生APP大部分公司还是不愿意将主营业务放到微信平台上,以免受制于腾讯,不过就小程序的应用场景(用完即走和二维码分发等)还是很值得我们学习的,技术上面如果了解React的话,会发现他们在组件化上面有很多雷同之处。说白了,小程序就是基于微信平台的H5轻应用,微信将系统底层功能(设备、位置、媒体、文件等)和微信自身功能(登录、支付、分享等)封装成相应API供小程序调用。
全栈程序员站长
2022/09/20
9350
微信小程序-开发入门(一)
推荐阅读
相关推荐
3-了解.json文件
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档