Cosmos DB是微软的一个云数据库产品,提供可扩展和高性能的服务。核心产品运行在一个专有的NoSQL数据库上,对于有经验的MongoDB开发者来说应该是很熟悉的。除了核心的Cosmos DB API之外,微软还提供了几个API。这些包括用于以下方面的API。
- SQL
- MongoDB
- Gremlin
- 卡桑德拉
向无服务器数据库操作的转变是迁移的最明显优势之一。Cosmos DB可以根据负载自动扩展你的吞吐量,而不是传统的提前配置吞吐量。
作为开发者,我们需要注意如何保护休息时的数据。在这篇文章中,您将了解到Cosmos DB如何帮助保护静态数据,以及您可以做什么来从您打算存储在Cosmos DB的数据中删除敏感数据,如个人身份信息(PII)。
PII的安全应用
我在开发领域的第一份工作是编写应用程序,对来自客户的个人数据执行scrubbing 。实际上,我们将收到包括个人姓名、社会安全号码和地址的数据。然后,这些数据将被发送到消费者报告公司进行验证,该公司将返回一个结果,让我们知道该地址是否是最新的,以及我们收到的信息是否正确。一旦数据返回,我们可以将结果导入我们的软件,如果刷新成功,那么该账户就是一个有效的联系人。
在本教程中,你将完成同样的过程。你将在.NET 6上建立一个ASP.NET Core网络应用程序,接受一个包含人名和社保号码的CSV文件。你将把它发送到一个假的刷卡服务。然后你将在一个Cosmos DB账户中记录结果。但你的老板说,你不能存储社会安全号码。因此,你将学习如何在存储到Cosmos DB之前从你的数据模型中删除该信息。
要继续下去,你将需要。
- Visual Studio 2022
- .NET 6
- 一个免费的Okta开发者账户(以处理你的OAuth需求)
- Okta CLI
- 一个有订阅的Azure账户
当然,如果你只是想看看代码,可以在Github上查看。
Cosmos DB概述
像大多数平台即服务(PaaS)产品一样,Cosmos DB为安全和性能提供了许多功能。这里有太多的功能可以列出。在微软无尽的迷宫般的文档中寻找文档,已经很有挑战性。但我确实想谈一谈几个亮点。
首先,作为一个PaaS,微软对网络控制、主机基础设施和物理安全负责。他们与你分享应用程序级别的控制和身份访问,以及数据分类。端点保护是你的责任。
但需要注意的是,Azure是符合HIPPA的。归根结底,你选择存储的数据是你的责任,并受到法律和法规的约束,这不在本文的讨论范围之内。
你也应该花点时间熟悉一下Cosmos DB的资源模型。我不会在这里深入探讨,但简短的说,一个database account 拥有一个database 。database 包含containers 。Containers 可以包含items ,stored procedures ,triggers ,和其他对象。在本教程中,你将在Azure门户上创建数据库账户,但你将从你的Web应用中创建数据库、容器和项目。
最后我想说的是,不同的Cosmos DB API有不同的方法来删除或加密数据。在本教程中,你将使用核心的Cosmos DB API,但我鼓励你去探索其他的API。
创建你的Okta应用程序
在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register ,以注册一个新账户。如果您已经有一个账户,运行okta login 。然后,运行okta apps create 。选择默认的应用程序名称,或根据你的需要进行更改。 选择Web,然后按回车键。
选择ASP.NET Core。 然后,将重定向URI改为http://localhost:5001/authorization-code/callback ,并使用http://localhost:5001/signout/callback ,作为注销重定向URI。
请注意,TCP 5001端口必须与应用程序使用的相同。你可以在终端显示的信息中看到它,当你用以下命令启动应用程序时
dotnet run.
Okta CLI是做什么的?
Okta CLI将在您的Okta机构中创建一个OIDC网络应用。它将添加您指定的重定向URI,并授予Everyone组的访问权限。当它完成后,您会看到如下输出。
Okta application configuration has been written to: /path/to/app/.okta.env
运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。
export OKTA_OAUTH2_ISSUER="https://dev-133337.okta.com/oauth2/default"
export OKTA_OAUTH2_CLIENT_ID="0oab8eb55Kb9jdMIr5d6"
export OKTA_OAUTH2_CLIENT_SECRET="NEVER-SHOW-SECRETS"
您的Okta域是发行者的第一部分,在/oauth2/default 。
注意:你也可以使用Okta管理控制台来创建你的应用程序。更多信息请参见创建一个ASP.NET Core应用程序。
创建一个Cosmos DB账户
正如我之前提到的,创建你的数据库、容器和项目的逻辑将在你的应用程序中。然而,你确实需要创建一个数据库账户来连接。
导航到Azure门户,选择创建一个资源。搜索Azure Cosmos DB ,并选择该选项。按照提示,在Azure Cosmos DB营销页面上选择创建。
接下来,你会看到一个页面,询问哪个API最适合你的工作负载。找到核心(SQL)-推荐,然后按创建。
创建你的网络应用程序
现在你可以把你的注意力转向创建你的网络应用程序。打开Visual Studio,按创建一个新项目。找到ASP.NET Core Web App(模型-视图-控制器)的模板,并按下一步。将你的应用程序命名为Okta_CosmosDb ,并按下一步。最后,从你的框架中选择**.NET 6.0(长期支持),然后按创建**。让Visual Studio花点时间来构建项目。
接下来,你可以在你的项目中安装你需要的两个包。
Install-Package Okta.AspNetCore -Version 4.1.0
Install-Package Microsoft.Azure.Cosmos -Version 3.26.1
Okta.AspNetCore 启用Okta,将使用Okta提供的中间件完成连接您的网络应用程序和Okta的所有繁重工作。这个包只需要使用先前设置的应用程序输出到 的值进行快速配置。.okta.env
Microsoft.Azure.Cosmos 提供了用于访问Cosmos DB API的核心库。你将使用这个包来创建你的数据库,为它添加一个容器,并在容器中插入项目。
接下来,打开appsettings.Development.json ,把那里的代码替换成以下内容。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Okta": {
"ClientId": "{yourClientId}",
"Domain": "{yourOktaDomain}",
"ClientSecret": "{yourClientSecret}"
},
"CosmosDb": {
"Account": "{yourCosmosDbUri}",
"Key": "{yourCosmosDbPrimaryKey}",
"DatabaseName": "oktacosmos",
"ContainerName": "Results"
}
}
你可以在.okta.env ,当你初始化你的Okta应用程序时,CLI产生的Okta值。要找到你的Cosmos值,请导航到你的Cosmos DB账户页面并打开Settings > Keys 标签。在这里你会找到URI,PRIMARY KEY, 和其他你可能需要的值。
接下来,将Program.cs 中的代码替换为以下内容。
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Okta.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
})
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOktaMvc(new OktaMvcOptions
{
// Replace these values with your Okta configuration
OktaDomain = builder.Configuration.GetValue<string>("Okta:Domain"),
ClientId = builder.Configuration.GetValue<string>("Okta:ClientId"),
ClientSecret = builder.Configuration.GetValue<string>("Okta:ClientSecret"),
Scope = new List<string> { "openid", "profile", "email" },
PostLogoutRedirectUri = "/"
});
builder.Services.AddScoped<Okta_CosmosDb.Services.IScrubService, Okta_CosmosDb.Services.ScrubService>();
builder.Services.AddSingleton<Okta_CosmosDb.Services.ICosmosService>(Okta_CosmosDb.Services.CosmosService.InitializeCosmosClientInstanceAsync(builder.Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
这些代码大部分是Visual Studio脚手架过程中的模板,但你要添加一些额外的项目。
首先,你需要配置你的认证以使用AddOktaMvc ,并从你的appsettings 文件中提供配置值。
接下来,你要为你即将编写的几个自定义服务设置依赖性注入。这些是IScrubService ,它将处理你的刷新过程,以及ICosmosService ,它将处理与你的Cosmos DB账户的通信。
最后,你正在调用InitializeCosmosClientInstanceAsync ,它将设置你的数据库和容器(如果它们不存在)。然后它将返回CosmosService ,作为一个单子。
创建你的应用服务
在项目根目录下创建一个新的文件夹,并将其命名为Services 。你将在这个目录中添加以下四个文件。
ICosmosService.csCosmosService.csIScrubService.csScrubService.cs
从替换两个接口中的代码开始。首先,将ICosmosService.cs 中的代码替换为以下内容。
namespace Okta_CosmosDb.Services
{
public interface ICosmosService
{
Task SaveResultAsync(Models.ScrubResult result);
}
}
接下来,用下面的代码替换IScrubService.cs 中的代码。
namespace Okta_CosmosDb.Services
{
public interface IScrubService
{
Task<Models.ScrubResult> ScrubAsync(Models.Person person);
}
}
现在你可以在各自的类中实现这些接口。首先,打开CosmosService.cs ,用下面的代码更新那里的代码。
using Okta_CosmosDb.Models;
using Microsoft.Azure.Cosmos;
namespace Okta_CosmosDb.Services
{
public class CosmosService : ICosmosService
{
private Container _container;
public CosmosService(
CosmosClient dbClient,
string databaseName,
string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
/// <summary>
/// Creates a Cosmos DB database and a container with the specified partition key.
/// </summary>
/// <returns></returns>
public static async Task<CosmosService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection)
{
string databaseName = configurationSection.GetSection("DatabaseName").Value;
string containerName = configurationSection.GetSection("ContainerName").Value;
string account = configurationSection.GetSection("Account").Value;
string key = configurationSection.GetSection("Key").Value;
CosmosClient client = new CosmosClient(account, key);
CosmosService cosmosDbService = new CosmosService(client, databaseName, containerName);
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
return cosmosDbService;
}
public async Task SaveResultAsync(ScrubResult result)
{
await this._container.CreateItemAsync<ScrubResult>(result, new PartitionKey(result.Id));
}
}
}
这个服务做了两个任务。首先,它使用SaveResultAsync 方法在Cosmos DB中存储数据。这只是调用你正在操作的容器并在该容器中创建一个项目。
这个类还包含静态的InitializeCosmosClientInstanceAsync 方法,你的Program.cs 调用它来返回服务。任何时候你试图访问一个ICosmosService ,你的应用程序将使用这个方法返回一个单子。这个方法将确保你的数据库和你的容器在将CosmosService 的实例传递给消费者使用之前已经创建。
接下来打开ScrubService.cs ,用这个实现替换那里的代码。
using Okta_CosmosDb.Models;
namespace Okta_CosmosDb.Services
{
public class ScrubService : IScrubService
{
public async Task<ScrubResult> ScrubAsync(Person person)
{
var task = Task.Run(() => { return new ScrubResult(person, new Random().Next(2) == 0); });
return await task;
}
}
}
这个类的作用是模拟一个真正的擦边球服务。这里的方法是async ,因为在真实世界的环境中会是这样的,但是由于你只是模拟服务,你可以把登录包在Task.Run ,以模拟一个async 。
创建你的模型
接下来,你将需要几个数据模型来促进你的视图,并在你的应用程序中传递。在你的Models 文件夹中为ScrubResult.cs 添加一个文件,并在其中添加以下代码。
using Newtonsoft.Json;
namespace Okta_CosmosDb.Models
{
public class ScrubResult
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "person")]
public Person Person { get; set; }
[JsonProperty(PropertyName = "success")]
public bool Success { get; set; }
public ScrubResult(Person person, bool success)
{
Person = person;
Success = success;
Id = Guid.NewGuid().ToString();
}
}
}
现在在你的Models 文件夹中添加Person.cs 的文件和代码。
using Newtonsoft.Json;
namespace Okta_CosmosDb.Models
{
public class Person
{
public string Name { get; set; }
[JsonIgnore]
public string SSN { get; set; }
}
}
这里有几件事情要做,你应该明白。首先,你将在你的Cosmos数据库中存储ScrubResult 对象。每个属性都使用JsonProperty 属性明确地给出了一个名称,然而你不需要这样做。你确实需要一个叫做id 的字段。 JsonProperty 属性有助于保持你的C#代码一贯的Pascal大小写,同时保持你的Cosmos DB属性名称的camel大小写。
这里真正的关键是,API使用Newtonsoft.Json 包将对象序列化为JSON字符串。这意味着你可以使用Newtonsoft包中的任何功能来操作你的数据。这就是你在Person 对象上所做的,你使用JsonIgnore 属性来隐藏SSN。
虽然JsonIgnore 是从这个对象中删除SSN的最简单的方法,但还有许多其他方法。你甚至可以使用Newtonsoft.Json.Serialization.DefaultContractResolver ,创建一个自定义属性,并对该字段进行散列、加密,或以其他方式转化为不太敏感的东西。
添加控制器逻辑
现在你可以在你的应用程序中添加你的控制器逻辑。
首先,在你的Controllers 目录中添加一个名为ImportController.cs 的类。将代码改为以下内容。
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Okta_CosmosDb.Controllers
{
[Authorize]
public class ImportController : Controller
{
Services.IScrubService _scrubService;
Services.ICosmosService _cosmosService;
public ImportController(
Services.IScrubService scrubService,
Services.ICosmosService cosmosService
)
{
_scrubService = scrubService;
_cosmosService = cosmosService;
}
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Upload(IFormFile csvFile)
{
List<Models.Person> persons = new List<Models.Person>();
using (var stream = csvFile.OpenReadStream())
using (StreamReader sr = new StreamReader(stream))
{
while (!sr.EndOfStream)
{
string[] rows = sr.ReadToEnd().Split(Environment.NewLine);
for(int i =0; i < rows.Length; i++)
{
if(i == 0)
{
//header row
continue;
}
var row = rows[i].Split(',');
persons.Add(new Models.Person()
{
Name = row[0],
SSN = row[1]
});
}
}
}
List<Models.ScrubResult> results = new List<Models.ScrubResult>();
foreach(var person in persons)
{
results.Add(_scrubService.Scrub(person));
}
foreach(var result in results)
{
_cosmosService.SaveResultAsync(result);
}
return View(results);
}
}
}
这个控制器将作为导入页面的一个视图。它还将接受一个CSV文件,将该文件转换为Person 对象的列表,然后针对每个人运行刷新过程。一旦刷新过程完成,它将使用你之前设置的数据库将结果保存到你的Cosmos DB账户。
接下来,你将需要一个控制器来记录用户的进入和退出。添加一个名为AccountController.cs 的控制器,并将代码改为以下内容。
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication.Cookies;
using Okta.AspNetCore;
namespace Okta_CosmosDb.Controllers
{
public class AccountController : Controller
{
public IActionResult SignIn()
{
if (!(HttpContext.User?.Identity?.IsAuthenticated ?? false))
{
return Challenge(OktaDefaults.MvcAuthenticationScheme);
}
return RedirectToAction("Index", "Import");
}
[HttpPost]
public IActionResult SignOut()
{
return new SignOutResult(new[] { OktaDefaults.MvcAuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme });
}
}
}
这个控制器包含了按照Okta的建议实现的SignIn 和SignOut 的方法。
最后,用下面的代码替换HomeController.cs 中的代码。
using Microsoft.AspNetCore.Mvc;
using Okta_CosmosDb.Models;
using System.Diagnostics;
namespace Okta_CosmosDb.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
if ((User?.Identity?.IsAuthenticated ?? false))
return RedirectToAction("Index", "Import");
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
这里你有Index 方法,但逻辑被替换为将认证的用户重定向到导入界面。
添加和编辑你的视图
第一个要编辑的视图是你的Views/Shared 文件夹中的_Layout.cshtml 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Okta With Cosmos DB</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Okta - Cosmos DB</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
</ul>
@if ((User?.Identity?.IsAuthenticated ?? false))
{
<form asp-action="SignOut" asp-controller="Account" method="post">
<button type="submit" href="Account/Logout" class="btn btn-primary my-2 my-sm-0">Logout</button>
</form>
}
else
{
<a href="Account/SignIn" class="btn btn-primary my-2 my-sm-0">Login</a>
}
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2022 - <a href="https://profile.fishbowlllc.com" target="_blank" rel="noreferrer">Nik Fisher</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
大部分的模板布局是好的,但它不包括Login 或Logout 按钮。你把这些添加到导航条上,并根据用户的认证状态显示适当的一个。
接下来,用下面的代码替换Home\Index.cshtml 。
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
A small tutorial on protecting PII in Microsoft's <a href="https://azure.microsoft.com/en-us/services/cosmos-db/"> Azure Cosmos DB</a>.
<br />Secured by <a href="https://www.okta.com/free-trial/">Okta.</a> <br />
Written by <a href="https://github.com/nickolasfisher"> Nik Fisher.</a>
</div>
这只是一个小的主页,包含一些关于教程的信息。
最后,在你的Views 文件夹中为Import 创建一个新的文件夹(如果还没有创建)。添加一个名为Index.cshtml 的文件,代码如下。
@(ViewData["Title"] = "Import Clients")
<form method="POST" asp-controller="Import" asp-action="Upload" enctype="multipart/form-data">
<input type="file" name="csvFile" class="btn-outline-primary btn" />
<input type="submit" class="btn btn-primary" value="Import" />
</form>
这个简单的页面为用户提供了一个导入CSV文件并将其提交给服务器的机会。
接下来,为Upload.cshtml 添加一个文件,该文件将在结果回来时显示。
@model List<Okta_CosmosDb.Models.ScrubResult>
@(ViewData["Title"] = "Scrub Results")
<table class="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Result</th>
</tr>
</thead>
<tbody>
@foreach (var result in Model)
{
<tr>
<td>@result.Person.Name</td>
<td>@result.Success</td>
</tr>
}
</tbody>
</table>
结论
作为开发者,我们总是需要考虑保护用户的数据。如果使用得当,PaaS和SaaS平台的兴起已经降低了风险。但是,随着这些新平台的出现,我们必须确保我们使用最佳实践,并对我们选择保留的数据保持关注。
在本教程中,你学到了如何从ASP.NET Core应用程序中存储数据到Cosmos DB。你学会了如何设置你的Cosmos DB账户,以及如何设置你的应用程序以创建数据库和容器来存储你的项目。最后,你使用JSON功能,在存储前从你的数据模型中删除敏感数据。