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

第 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 接口定义定制的需求,这样就可以添加自定义验证逻辑而不会影响各个控制器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311884.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

里加一列为1_9月1号新宠物食品法规实施啦,辣鸡宠物食品遭殃,你也可能违法...

大家好啊&#xff0c;今天是2019年9月1号&#xff0c;对于宠物行业其实是一个非常特别的日子今天宠物饲料管理办法正式实施加上2019年1月1号实施的宠物饲料卫生规定以及2015年3月8号实施的全价宠物食品 犬粮&#xff0c;猫粮标准中国的所有的猫狗宠物食品在今天有法可依&#x…

[蓝桥杯2016初赛]凑算式-dfs,next_permutation

代码如下&#xff1a; #include <iostream> using namespace std; const int N 15; bool st[N]; double a[N];int cnt; void dfs(int u) {if (u 10) {if (a[1] a[2] / a[3] (a[4] * 100 a[5] * 10 a[6]) / (a[7] * 100 a[8] * 10 a[9]) 10) {cnt;}}for (int i …

word List 12

word List 12 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

软硬件协同编程 - C#玩转CPU高速缓存(附示例)

写在前面好久没有写博客了&#xff0c;一直在不断地探索响应式DDD&#xff0c;又get到了很多新知识&#xff0c;解惑了很多老问题&#xff0c;最近读了Martin Fowler大师一篇非常精彩的博客The LMAX Architecture&#xff0c;里面有一个术语Mechanical Sympathy&#xff0c;姑且…

python操作excel_使用Python操作Excel时必学的3个库

Python对Excel的操作我主要用xlwt、xlrd、xlutils这三个库。1、xlwt主要用来创建并写入数据到Excel。已经存在的表不可以写入。以下使用Python写九九乘法表到Excel运行之后&#xff0c;代码文件所在的文件夹会多出一个”九九乘法表“的Excel&#xff0c;内容如下图&#xff1a;…

线性代数---线性方程组

线性代数—线性方程组 常见题型的解题技巧 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

gRPC in ASP.NET Core 3.x -- Protocol Buffer, Go语言的例子(下)

前两篇文章半年前写的&#xff1a;gRPC in ASP.NET Core 3.0 -- Protocol Buffer&#xff08;1&#xff09;&#xff0c;gRPC in ASP.NET Core 3.0 -- Protocol Buffer&#xff08;2&#xff09;这是上一篇&#xff1a;gRPC in ASP.NET Core 3.x -- Protocol Buffer, Go语言的例…

得到选择框句柄 怎么操作_知道借名买房有风险,只能选择借名买房该怎么操作?...

文/楼市大家谈(quanadcom)正常情况下&#xff0c;购房者买房之时最看重房子的产权&#xff0c;因此无论是签订购房合同还是办理房产证时&#xff0c;最为在意的都是以谁的名义买房&#xff0c;房子究竟记在谁的名下&#xff0c;但是在有些情况下&#xff0c;一些人迫于无奈&…

数据结构---平衡二叉树

数据结构—平衡二叉树 原理&#xff1a;参考趣学数据结构 代码&#xff1a; #include<stdio.h> #include<stdlib.h> typedef struct avlTree {int data;struct avlTree *lchild, *rchild;int height; }avlTree;int height(avlTree* AVLTree);//声明avl树的深度 …

python list转换成array_一文掌握Python【不定期更新】

目录一、Numpy1 基本操作2 随机数3 打乱训练数据4 得到元素的最值5 拼接数组6 得到函数的信息7 得到累乘即各项相乘的结果8 判断一个数是否在数组中9 数组的变换10 排序11 元素的筛选12.保存文件/打开文件13.限制范围二、PIL1.安装2.PIL与Numpy的互相转化3.获取Image信息4.打开…

开源netcore前后端分离,前端服务端渲染方案

SPA单页面应用容器 开源地址&#xff1a; https://github.com/yuzd/Spa功能介绍前端应用开发完后打包后自助上传部署发布配合服务端脚本(javascript)实现服务端业务逻辑编写渲染SSR功能可以快速回滚到上一个版本可以设置环境变量供SSR功能使用服务端脚本提供执行日志 redis db三…

[蓝桥杯2016初赛]方格填数-next_permutation

代码如下&#xff1a; #include <iostream> #include <algorithm> using namespace std;int main() {int a[10] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};int cnt 0;do {if ((abs(a[0] - a[1]) ! 1) && (abs(a[1] - a[2]) ! 1) && (abs(a[3] - a[4]) ! …

word List 13

word List 13 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

ASP.NetCore+VUE 实现学生成绩管理系统(一)

周三陪伴是最长情的告白还有两天情人节&#xff1a;「无论是在家里&#xff0c;还是在工作&#xff0c;或者是在自我防护中&#xff0c;多给家人爱人发句平安&#xff0c;是最有心意、最重要的一件事。」♥感谢老李????近来一段时间一直没有学习新的东西&#xff0c;闲暇的…

下拉菜单实现树状结构_树形图:复杂层次结构的数据可视化

树形图&#xff1a;复杂层次结构的数据可视化作者&#xff1a;Page Laubheimer[1]树形图是一种复杂的&#xff0c;基于区域的数据可视化&#xff0c;用于复杂层次结构的数据&#xff0c;可能难以精确解释。在许多情况下&#xff0c;最好使用更简单的可视化效果&#xff08;例如…

[蓝桥杯2016决赛]路径之谜

题目描述 小明冒充X星球的骑士&#xff0c;进入了一个奇怪的城堡。城堡里边什么都没有&#xff0c;只有方形石头铺成的地面。 假设城堡地面是 n x n 个方格。 按习俗&#xff0c;骑士要从西北角走到东南角。可以横向或纵向移动&#xff0c;但不能斜着走&#xff0c;也不能跳跃…

数据结构---二叉平衡排序树的删除

数据结构—二叉平衡排序树的删除 原理&#xff1a;参考趣学数据结构 代码&#xff1a; #include<stdio.h> #include<stdlib.h> typedef struct avlTree {int data;struct avlTree *lchild, *rchild;int height; }avlTree;int height(avlTree* AVLTree);//声明av…

Magicodes.IE 2.0发布

Magicodes.IE是我们维护的开源的导入导出通用库&#xff0c;去年年底已加入NCC开源组织。Github地址&#xff1a;https://github.com/xin-lai/Magicodes.IEMagicodes.IE不是一蹴而就&#xff0c;而是根据实际需求不断迭代出来的&#xff0c;而且历经多次重构。这一次&#xff0…

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

第 11 章 开发实时应用和服务在本章&#xff0c;我们将讨论“实时”的准确含义&#xff0c;以及在大部分消费者看来应该属于这一范畴的应用类型接着&#xff0c;我们将探讨 WebSocket&#xff0c;并分析为什么传统的 WebSocket 与云环境完全不相适应&#xff0c;最后我们将构建…

word List 14

word List 14 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;