第 10 章 应用和微服务安全

云应用意味着应用运行所在的基础设施无法掌控,因此安全不能再等到事后再考虑,也不能只是检查清单上毫无意义的复选框

由于安全与云原生应用密切相关,本章将讨论安全话题,并用示例演示几种保障 ASP.NET Core Web 应用和微服务安全的方法

云环境中的安全

内网应用

企业一直在开发这种支持性的应用,但当我们需要基于运行在可缩放的云基础设施之的 PaaS 开发此类应用时,很多旧的模式和实践将很快失效

一个最明显的问题就是无法支持 Windows 身份验证

长期以来,ASP.NET 开发人员一直沉浸在借助内置的 Windows 凭据来保障 Web 应用安全的便利中

不管是公有云平台还是私有部署的 PaaS 平台,在这些平台上,支撑应用的操作系统应被视为临时存续的

有些企业的安全策略要求所有虚拟机在滚动更新期间需要销毁并重新构建,从而缩小持续攻击的可能范围

Cookie 和 Forms 身份验证

当应用运行于 PaaS 环境中时,Cookie 身份验证仍然适用

不过它也会给应用增加额外负担

首先,Forms 身份验证要求应用对凭据进行维护并验证

也就是说,应用需要处理好这些保密信息的安全保障、加密和存储

云环境中的应用内加密

在传统 ASP.NET 应用开发中,常见的加密使用场景是创建安全的身份验证 Cookie 和会话 Cookie

在这种加密机制中,Cookie 加密时会用到机器密钥

然后当 Cookie 由浏览器发回 Web 应用时,再使用同样的机器密钥对其进行解密

如果无法依赖持久化文件系统,又不可能在每次启动应用时将密钥置于内存中,这些密钥将如何存储

答案是,将加密密钥的存储和维护视为后端服务

也就是说,与状态维持机制、文件系统、数据库和其他微服务一样,这个服务位于应用之外

Bearer 令牌

本章的示例将讲解 OAuth 和 OpenID Connect (简称 OIDC)

如果要以 HTTP 友好、可移植的方式传输身份证明,最常见的方法就是 Bearer 令牌

应用从 Authorization 请求头接收 Dearer 令牌

下例展示一个包含 Bearer 令牌的 HTTP 跟踪会话

POST /api/service HTTP/1.1
Host: world-domination.io
Authorization: Bearer ABC123HIJABC123HIJABC123HIJ Content-Type:
application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (XLL; Linux x86_64) etc...etc...etc...

Authorization 请求头的值中包含一个表示授权类型的单词,紧接着是包含凭据的字符序列

通常,服务在处理 Bearer 令牌时,会从 Authorization 请求头提取令牌

很多各式的令牌,例如 OAuth 2.0 (JWT),通常将 Base64 编码用作一种 URL 友好格式,因此验证令牌的第一步就是解码,以获取原有内容

如果令牌使用私钥加密,服务就需要使用公钥验证令牌确实由正确的发行方颁发

ASP.NET Core Web 应用安全

本章示例中,我们将主要关注 OpenID Connetc 和 JWT 格式的 Bearer 令牌

OpenID Connect 基础

OpenID Connect 是 OAuth2 的一个超集,它规定了身份提供方(IDP)、用户和应用之间的安全通信的规范和标准

使用 OIDC 保障 ASP.NET Core 应用的安全

作为本章第一个代码清单,我们将使用 OIDC 为一个简单的 ASP.NET Core
MVC Web 应用提供安全保障功能

创建一个空的 Web 应用

$ dotnet new mvc

使用 Auth0 账号配置身份提供方服务

现在可转到 http://auth0.com/,注册完成后进入面板,点击“创建客户端”按钮,请确保应用类型选择为“常规 Web 应用”

选择 ASP.NET Core 作为实现语言后,将转到一个 “快速开始”教程,其代码与本章将要编写的内容非常相似

使用 OIDC 中间件

GitHub链接:https://github.com/microservices-aspnetcore/secure-services

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Http;namespace StatlerWaldorfCorp.SecureWebApp
{public class Startup{public Startup(IHostingEnvironment env){var builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath).AddJsonFile("appsettings.json", optional: false, reloadOnChange: false).AddEnvironmentVariables();Configuration = builder.Build();}public IConfigurationRoot Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.AddAuthentication(options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);// Add framework services.services.AddMvc();services.AddOptions();services.Configure<OpenIDSettings>(Configuration.GetSection("OpenID"));}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerFactory,IOptions<OpenIDSettings> openIdSettings){Console.WriteLine("Using OpenID Auth domain of : " + openIdSettings.Value.Domain);loggerFactory.AddConsole(Configuration.GetSection("Logging"));loggerFactory.AddDebug();if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();app.UseCookieAuthentication( new CookieAuthenticationOptions{AutomaticAuthenticate = true,AutomaticChallenge = true});var options = CreateOpenIdConnectOptions(openIdSettings);options.Scope.Clear();options.Scope.Add("openid");options.Scope.Add("name");options.Scope.Add("email");options.Scope.Add("picture");app.UseOpenIdConnectAuthentication(options);app.UseMvc(routes =>{routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");});}private OpenIdConnectOptions CreateOpenIdConnectOptions(IOptions<OpenIDSettings> openIdSettings){return new OpenIdConnectOptions("Auth0"){Authority = $"https://{openIdSettings.Value.Domain}",ClientId = openIdSettings.Value.ClientId,ClientSecret = openIdSettings.Value.ClientSecret,AutomaticAuthenticate = false,AutomaticChallenge = false,ResponseType = "code",CallbackPath = new PathString("/signin-auth0"),ClaimsIssuer = "Auth0",SaveTokens = true,Events = CreateOpenIdConnectEvents()};}private OpenIdConnectEvents CreateOpenIdConnectEvents(){return new OpenIdConnectEvents(){OnTicketReceived = context =>{var identity =context.Principal.Identity as ClaimsIdentity;if (identity != null) {if (!context.Principal.HasClaim( c => c.Type == ClaimTypes.Name) &&identity.HasClaim( c => c.Type == "name"))identity.AddClaim(new Claim(ClaimTypes.Name, identity.FindFirst("name").Value));}return Task.FromResult(0);}};}}
}

与之前各章代码的第一点区别在于,我们创建了一个名为 OpenIdSettings 的选项类,从配置系统读入后,以 DI 的服务方式提供给应用

它是一个简单类,其属性仅用于存储每种 OIDC 客户端都会用到的四种元信息:

  • 授权域名

  • 客户端 ID

  • 客户端密钥

  • 回调 URL

由于这些信息的敏感性,我们的 appsettings.json 文件没有签入到 GitHub,不过以下代码清单列出了它的大致格式

{"OpenID": {"Domain": "Your Auth0 domain","ClientId": "Your Auth0 Client Id","ClientSecret": "Your Auth0 Client Secret","CallbackUrl": "http://localhost:5000/signin-auth0"}
}

接下来要在 Startup 类中执行的两部操作是,让 ASP.NET Core 使用 Cookie 身份验证和 OpenID Connect 身份验证

添加一个 account 控制器,提供的功能包括登录、注销、以及使用一个视图显示用户身份中的所有特征

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Authorization;
using System.Linq;
using System.Security.Claims;namespace StatlerWaldorfCorp.SecureWebApp.Controllers
{public class AccountController : Controller{public IActionResult Login(string returnUrl = "/"){return new ChallengeResult("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });}[Authorize]public IActionResult Logout(){HttpContext.Authentication.SignOutAsync("Auth0");HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);return RedirectToAction("Index", "Home");}[Authorize]public IActionResult Claims(){ViewData["Title"] = "Claims";var identity = HttpContext.User.Identity as ClaimsIdentity;ViewData["picture"] = identity.FindFirst("picture").Value;return View();}}
}

Claims 视图代码,它从特征集合中逐个取出特征的类型和值,并呈现在表格中,同时,视图还显示用户头像

<div class="row"><div class="col-md-12"><h3>Current User Claims</h3><br/>  <img src="@ViewData["picture"]" height="64" width="64"/><br/><table class="table"><thead><tr><th>Claim</th><th>Value</th></tr></thead><tbody>@foreach (var claim in User.Claims){<tr><td>@claim.Type</td><td>@claim.Value</td></tr>}</tbody></table></div>
</div>

现在,我们已经基于一个模板生成的空白 ASP.NET Core Web 应用,建立了与第三方云友好的身份提供服务的连接

这让云应用能够利用 Bearer 令牌和 OIDC 标准的优势,从手工管理身份验证的负担中解放出来

OIDC 中间件和云原生

我们已经讨论过在使用 Netflix OSS 技术栈时,如何借助 Steeltoe 类库支持应用配置和服务发现

我们可以使用来自 Steeltoe 的 NuGet 模块 Steeltoe.Security.DataProtection.Redis

它专门用于将数据保护 API 所用的存储从本地磁盘迁移到外部的 Redis 分布式缓存中

在这个类库,可使用以下方式在 Startup 类的 ConfigureServices 方法中配置由外部存储支持的数据保护功能

services.AddMvc();services.AddRedisConnectionMultiplexer(Configuration);
services.AddDataProtection().PersisitKeysToRedis().SetApplicationName("myapp-redis-keystore");services.AddDistributedRedisCache(Configuration);services.AddSession();

接着,我们在 Configure 方法中调用 app.UseSession() 以完成外部会话状态的配置

保障 ASP.NET Core 微服务的安全

本节,我们讨论为微服务提供安全保障的几种方法,并通过开发一个使用 Bearer 令牌提供安全功能的微服务演示其中的一种方法

使用完整 OIDC 安全流程保障服务的安全

在这个流程中,用户登录的流程前面已经讨论过,即通过几次浏览器重定向完成网站和 IDP 之间的交互

当网站获取到合法身份后,会向 IDP 申请访问令牌,申请时需要提供身份证令牌以及正在被请求的资源的信息

使用客户端凭证保障服务的安全

首先,只允许通过 SSL 与服务通信

此外,消费服务的代码需要在调用服务时附加凭据

这种凭据通常就是用户名和密码

在一些不存在人工交互的场景中,将其称为客户端标识和客户端密钥更准确

使用 Bearer 令牌保障服务的安全

在服务的 Startup 类型的 Configure 方法中启用并配置 JWT Bearer 身份验证

app.UseJwtBearerAuthentication(new JwtBearerOptions)
{AutomaticAuthenticate = true,AutomaticChallenge = true,TokenValidationParameters = new TokenValidationParameters{ValidateIssuerSigningKey = true,IssuerSigningKey = signingKey,ValidateIssuer = false,ValidIssuer = "http://fake.issuer.com",ValidateAudience = false,ValidAudience = "http://sampleservice.example.com",ValidateLifetime = true,}
};

我们可控制在接收 Bearer 令牌期间要执行的各种验证,包括颁发方签名证书、颁发方名称、接收名称以及令牌的时效

在上面的代码中,我们禁用了颁发方和接收方名称验证,其过程都是相当简单的字符串对比检查

开启验证时,颁发方和接收方名称必须与令牌中包含的颁发方式和接收方式名称严格匹配

要创建一个密钥,用于令牌签名时所用的密钥进行对比,我们需要一个保密密钥,并从它创建一个 SymmetricSecurityKey

string SecretKey = "sericouslyneverleavethissitting in yourcode";
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));

为了消费安全的服务,我们需要创建一个简单的控制台应用,它从一组 Claim 对象生成一个 JwtSecurityToken 实例,并作为 Bearer 令牌放入 Authorization 请求头发给服务端

var claims = new []
{new Claim(JwtRegisteredClaimNames.Sub, "AppUser_Bob"),new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(DataTime.Now).ToString(), ClaimValueTypes.Integer64),
};
var jwt = new JwtSecurityToken(issuer : "issuer",audience : "audience",claims : claims,notBefore : DateTiem.UtcNow,expires : DateTime.UtcNow.Add(TimeSpan.FromMinutes(20)),signingCredentials: creds)
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", encodedJwt);var result = httpClient.GetAsync("http://localhost:5000/api/secured").Result;
Console.WriteLine(result.StatusCode);
Console.WriteLine(result.Content.ToString());

下面是一个受安全机制保护的控制器方法,它将枚举从客户端发来的身份特征

[Authorize]
[HttpGet]
public string Get()
{foreach (var claim in HttpContext.User.Claims){Console.WriteLine($"{claim.Type}:{claim.Value}");}return "this is from the super secret area";
}

如果要控制特定客户端能够访问的控制器方法,我们可以利用策略概念,策略是在授权检查过程中执行一小段代码

[Authorize( Policy = "CheeseburgerPolicy")]
[HttpGet("policy")]
public string GetWithPolicy()
{return "this is from the super secret area w/policy enforcement.";
}

在 ConfigureServices 方法中配置策略的过程很简单

public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddOptions();services.AddAuthorization( options => {options.AddPolicy("CheeseburgePolicy",policy =>policy.RequireClaim("icanhazcheeseburger", "true"));});
}

现在,只要修改控制台应用,在其中添加这种类型的特征并将值指定为 true,就既能调用普通受保护的控制器方法,又能调用标记了 CheeseburgerPolicy 策略的方法

该策略需要特定的身份特征、用户名、条件以及角色

还可以通过实现 IAuthorizationRequirement 接口定义定制的需求,这样就可以添加自定义验证逻辑而不会影响各个控制器

《ASP.NET Core 微服务实战》-- 读书笔记(第10章)相关推荐

  1. 《ASP.NET Core 微服务实战》译者序

    最近,我将<ASP.NET Core 微服务实战>一书由英文翻译为中文.这本书是由清华大学出版社引进的,目前还处于最后的排版校对过程中,现将该书的译者序发表于此. 以下为译者译全文: &q ...

  2. 《ASP.NET Core 微服务实战》送书结果公告

    如何构建基于.NET Core和云环境下的微服务技术体系?的送书抽奖结果已经出来了: 当前只有一位同学填写了地址.其他几位同学抓紧填写,3/9 日还没有完成填写将作废,奖品可是热门的<ASP.N ...

  3. 《ASP.NET Core 微服务实战》-- 读书笔记(第7章)

    第 7 章 开发 ASP.NET Core Web 应用 ASP.NET Core 基础 在本章,我们将从一个命令行应用开始,并且在不借助任何模板,脚手架和向导的情况下,最终得到一个功能完整的 Web ...

  4. 《ASP.NET Core 微服务实战》-- 读书笔记(第3章)

    第 3 章 使用 ASP.NET Core 开发微服务 微服务定义 微服务是一个支持特定业务场景的独立部署单元.它借助语义化版本管理.定义良好的 API 与其他后端服务交互.它的天然特点就是严格遵守单 ...

  5. 《ASP.NET Core 微服务实战》-- 读书笔记(第1章 、第2章)

    译者序 微服务设计方法清晰定义了各个开发团队的业务边界,微服务框架以不同方式实现了服务之间的协作与集成. .NET Core 作为全新的 .NET 技术,它不仅完全开源.跨平台,更面向云原生开发进行了 ...

  6. 《ASP.NET Core 微服务实战》-- 读书笔记(第9章)

    第 9 章 微服务系统的配置 微服务系统中的配置需要关注更多其他方面的因素,包括: 配置值的安全读写 值变更的审计能力 配置信息源本身的韧性和可靠性 少量的环境变量难以承载大型.复杂的配置信息 应用要 ...

  7. 《ASP.NET Core 微服务实战》-- 读书笔记(第12章)

    第 12 章 设计汇总 微服务开发并不是要学习 C#.Java 或者 Go 编程--而是要学习如何开发应用以适应并充分利用弹性伸缩环境的优势,它们对托管环境没有偏好,并能瞬间启停 换句话说,我们要学习 ...

  8. 《ASP.NET Core 微服务实战》-- 读书笔记(第11章)

    第 11 章 开发实时应用和服务 在本章,我们将讨论"实时"的准确含义,以及在大部分消费者看来应该属于这一范畴的应用类型 接着,我们将探讨 WebSocket,并分析为什么传统的 ...

  9. 《ASP.NET Core 微服务实战》-- 读书笔记(第6章)

    第 6 章 事件溯源与 CQRS 在本章,我们来了解一下随着云平台一同出现的设计模式 我们先探讨事件溯源和命令查询职责分离(CQRS)背后的动机与哲学 事件溯源简介 事实由事件溯源而来 我们大脑就是一 ...

最新文章

  1. 过分!高校实验动物被学生私自放走,实验兔刚缝完针,连线都没拆......
  2. 【c语言】char类型变量分别以字符形式和整数形式输出
  3. wdcp3.2.6版 https全站跳转 标记待细化
  4. 【深度学习】论文EMO单眼识别分析
  5. prefixspan是挖掘频繁子序列,子序列不一定是连续的,当心!!!
  6. cacls 使用方法
  7. 视频跟踪——CMT算法
  8. gtone eclipse plugin install
  9. C++中模块(DLL)对外暴露接口的几种方式
  10. python3安装步骤-超详细的小白python3.X安装教程|Python安装
  11. 【Tensorflow】下载预训练模型和参数小结
  12. Hololens开发常见错误
  13. VS2010 SP1 编译QT4.8.0 x64版本
  14. 儿童“益”站线上课堂 战“疫”不停学
  15. 语音转文字软件哪个好,这三款值得收藏
  16. Catagory用法
  17. stream_kws_cnn
  18. python识别屏幕图像后点击操作_Python学习笔记——用GUI自动化控制键盘和鼠标
  19. 二月主题读书整理——元技能系列
  20. 谷歌浏览器如何关闭当前页面

热门文章

  1. ios开发第一步--虚拟机安装MAC OS X
  2. C# Socket编程笔记(转)
  3. Java 关于中文乱码处理的经验总结
  4. 导入shape文件到SDE数据库
  5. geek_愚蠢的怪胎技巧:在Windows 7中启用秘密的“ How-To Geek”模式
  6. 如何在OS X中打开或关闭鼠标定位器
  7. 删除microsoft_如何从您的Microsoft帐户中删除设备
  8. sharding-jdbc学习
  9. UITableView的使用及代理方法
  10. flash文件制作笔记