ASP.NET核心身份验证实验室
https://g*ithu*b.*com/dotnet-presentations
这是针对针对ASP.NET Core 2.1和VS2017/VS代码的ASP.NET Core Authentication Lab进行的。
该实验室使用模型视图控制器模板,因为这是每个人到现在一直在使用的,这是绝大多数人的最熟悉的起点。
官方身份验证文档在https://docs.m*i**crosoft.com/en-us/aspnet/core/security/authentication/。
授权实验室可从https://*git*hu*b.com/blowdart/aspnetauthorizationworkshop/tree/core2
步骤0:准备
先决条件
- Visual Studio 2017(社区版是免费的)或
- Visual Studio代码(免费代码)和
- .NET Core 2.1 SDK。
创建一个新的空白项目。
我们将从命令行开始。
- 在某个地方的计算机上创建一个目录,称其为AuthenticationLab
- 打开命令行/外壳,然后更改为您创建的目录
- 在命令行类型
dotnet new console - 完成类型的
dotnet run后,您将看到“ Hello World”
让我们检查一下创建的内容。该目录包含两个文件
authenticationlab.csproj-
Program.cs
类型code . VS代码将打开目录。它可能会安装一些缺少的东西。探索两个文件。
- CS Proj文件是项目文件,它包含有关如何构建项目以及应包括的指令
-
program.cs包含输出“ Hello World”的说明。
让我们将其转变为Web应用程序。 .NET Core是核心库和运行时。 ASP.NET Core增加了对Web应用程序的支持,包括Kestrel A Web服务器。
首先,让我们在应用程序中添加ASP.NET核心。
- 编辑
csproj文件并在关闭PropertyGroup元素之后添加以下内容
< ItemGroup > < PackageReference Include = \" Microsoft.AspNetCore.All \" Version = \" 2.1 \" /> </ ItemGroup >
- 您的
csproj现在应该看起来像这样
< Project Sdk = \" Microsoft.NET.Sdk \" > < PropertyGroup > < OutputType >Exe</ OutputType > < TargetFramework >netcoreapp2.1</ TargetFramework > </ PropertyGroup > < ItemGroup > < PackageReference Include = \" Microsoft.AspNetCore.All \" Version = \" 2.1 \" /> </ ItemGroup > </ Project >
- 保存
csproj文件。如果您使用的是Visual Studio或VS代码,则可能会提示还原软件包,请选择“是”。如果您使用的是命令行和编辑器,该编辑器会使您重新考虑诸如vim Enterdotnet restore在命令行中)的生活选择。不,我无法帮助您退出VIM。 - 现在,将编辑器切换到
program.cs文件。更改此文件的内容如下
using System ; using Microsoft . AspNetCore ; using Microsoft . AspNetCore . Hosting ; namespace authenticationlab { class Program { public static void Main ( string [ ] args ) { CreateWebHostBuilder ( args ) . Build ( ) . Run ( ) ; } public static IWebHostBuilder CreateWebHostBuilder ( string [ ] args ) => WebHost . CreateDefaultBuilder ( args ) . UseStartup < Startup > ( ) ; } }
- 请注意,
.UseStartup<Startup>()时间糟糕,它正在寻找一个名为startup类。因此,让我们补充一点;创建一个名为startup.cs的新文件,然后将以下文件粘贴到其中
using System ; using Microsoft . AspNetCore . Builder ; using Microsoft . AspNetCore . Hosting ; using Microsoft . AspNetCore . Http ; using Microsoft . Extensions . DependencyInjection ; namespace authenticationlab { public class Startup { public void ConfigureServices ( IServiceCollection services ) { } public void Configure ( IApplicationBuilder app , IHostingEnvironment env ) { app . Run ( async ( context ) => { await context . Response . WriteAsync ( \"Hello World!\" ) ; } ) ; } } }
- 使用您的IDE
dotnet build构建应用程序 - 使用您的IDE或使用
dotnet run运行应用程序 - 打开浏览器和浏览器http:// localhost:5000/
- 惊讶地看到Hello World!
- 恭喜您在.NET Core中具有Web应用程序。
步骤1:设置身份验证
对于此实验室,我们将设置一个使用Google登录的应用程序。
首先,我们需要配置HTTPS。
-
检查您的命令行中是否有开发人员证书运行
dotnet dev-certs https -c -v。如果您有证书,则会看到“找到有效的证书”。 -
如果没有证书是基金运行
dotnet dev-certs https --trust。您会看到Windows的弹出窗口询问您是否想信任localhost的证书。单击是,您现在将拥有证书。如果您使用Linux Trust不会在证书生成时间工作,则必须在使用的任何浏览器中信任它。如果您使用的是Firefox,则还必须在浏览器中信任它,因为Firefox不符合OS证书设置。 -
再次运行您的应用程序,但是这次浏览器访问https:// localhost:5001,您应该能够通过HTTPS连接。
-
您可以通过添加
app.UseHttpsRedirection();在您的Configure()方法的开头。 -
接下来,我们将使用Google创建一个应用程序,以支持Google登录
-
导航到https://developers.*go**ogle.com/identity/sign-in/web/sign-in,然后单击
Configure A Project按钮。创建一个称为coreeytenticationlab的新项目。 -
在配置您的OAuth客户端
Web Server上,从Where are you calling from?下拉并输入https:// localhost:5001/signin-google作为授权重定向的URI。 -
单击
Create。 -
从结果屏幕上记下您的客户端ID和客户端秘密,然后单击API控制台链接。
-
单击
Enable APIs and services按钮,然后在搜索框中输入Google+ API,然后选择它。单击Enable。 -
导航到https://console.deve*lopers.g**oogle.com/apis/dashboard,在屏幕顶部的下拉
-
返回您的代码,然后用以下代码替换
startup.cs,将客户ID和秘密放在选项属性中。
using System ; using System . Collections . Generic ; using System . Linq ; using System . Threading . Tasks ; using Microsoft . AspNetCore . Authentication ; using Microsoft . AspNetCore . Authentication . Cookies ; using Microsoft . AspNetCore . Authentication . Google ; using Microsoft . AspNetCore . Builder ; using Microsoft . AspNetCore . Hosting ; using Microsoft . AspNetCore . Http ; using Microsoft . Extensions . DependencyInjection ; namespace authenticationlab { public class Startup { public void ConfigureServices ( IServiceCollection services ) { services . AddAuthentication ( options => { options . DefaultAuthenticateScheme = CookieAuthenticationDefaults . AuthenticationScheme ; options . DefaultSignInScheme = CookieAuthenticationDefaults . AuthenticationScheme ; options . DefaultChallengeScheme = GoogleDefaults . AuthenticationScheme ; } ) . AddCookie ( ) . AddGoogle ( options => { options . ClientId = \"**CLIENT ID**\" ; options . ClientSecret = \"**CLIENT SECRET**\" ; } ) ; } public void Configure ( IApplicationBuilder app , IHostingEnvironment env ) { app . UseHttpsRedirection ( ) ; app . UseAuthentication ( ) ; app . Run ( async ( context ) => { if ( ! context . User . Identity . IsAuthenticated ) { await context . ChallengeAsync ( ) ; } await context . Response . WriteAsync ( \"Hello \" + context . User . Identity . Name + \"! \\r \" ) ; } ) ; } } }
- 运行代码,浏览Google登录屏幕,您应该看到“ Hello YourGoogleUsername !”
- 如果浏览器确定页面为HTML,
WriteAsync样品中有XSS攻击
context . Response . Headers . Add ( \"Content-Type\" , \"text/plain\" ) ;
步骤2:身份验证事件和记录
- 让我们添加一些记录以查看发生了什么。
- 在
Startup类中创建一个名为_logger的ILogger类型的私有类级变量。 - 添加一个构造函数,该构造函数采用
ILoggerFactory的参数,并为_logger创建并分配一个记录器。 (您需要添加对Microsoft.Extensions.Loggingusing引用,如果您的编辑不适合您。) - 这应该看起来像以下内容;
private ILogger _logger ; public Startup ( ILoggerFactory loggerFactory ) { _logger = loggerFactory . CreateLogger < Startup > ( ) ; }
- 现在,让我们通过在身份验证启动的事件中添加一些登录方式来查看Google身份验证服务上的事件。
- 事件是选项类的一部分(您可能需要
using Microsoft.AspNetCore.Authentication.OAuth;如果您的编辑器不提示您执行此操作)。
options . Events = new OAuthEvents ( ) { OnRedirectToAuthorizationEndpoint = context => { _logger . LogInformation ( \"Redirecting to {0}\" , context . RedirectUri ) ; return Task . CompletedTask ; } , OnRemoteFailure = context => { _logger . LogInformation ( \"Something went horribly wrong.\" ) ; return Task . CompletedTask ; } , OnTicketReceived = context => { _logger . LogInformation ( \"Ticket received.\" ) ; return Task . CompletedTask ; } , OnCreatingTicket = context => { _logger . LogInformation ( \"Creating tickets.\" ) ; return Task . CompletedTask ; } } ;
- 确保您的身份验证cookie已删除(关闭浏览器或手动挖掘),然后再次浏览到网站,然后观看控制台中的登录。
- 您注意到有什么区别吗?为什么您的用户名不再向您打招呼?
- 有些事件需要返回的东西。查看事件或来源的文档。
- 请注意,
OnRedirectToAuthorizationEndpoint默认实现将调用重定向 – 这不再在代码中发生,因此请添加回去;
OnRedirectToAuthorizationEndpoint = context => { _logger . LogInformation ( \"Redirecting to {0}\" , context . RedirectUri ) ; context . Response . Redirect ( context . RedirectUri ) ; return Task . CompletedTask ; } ,
- 现在,查看
OnCreatingTicket()内的上下文属性。其中有一些有用的内容,例如Google Access令牌。如果我想保存该怎么办? - 让我们存储访问令牌和刷新令牌Google为我们提供的身份,因为我们正在创建Insern
OnCreatingTicket声明。 - 首先,我们需要索赔的名称,因此在
Startup类中定义一些const值
private const string AccessTokenClaim = \"urn:tokens:google:accesstoken\" ;
- 现在,在
OnCreatingTicket事件中,让我们使用这些名称创建一些新的主张,并具有适当的值
OnCreatingTicket = context => { var identity = ( ClaimsIdentity ) context . Principal . Identity ; identity . AddClaim ( new Claim ( AccessTokenClaim , context . AccessToken ) ) ; _logger . LogInformation ( \"Creating tickets.\" ) ; return Task . CompletedTask ; }
- 最后,要检查他们在问候后持续添加
app.Run()lambda中的一些代码;
var claimsIdentity = ( ClaimsIdentity ) context . User . Identity ; var accessTokenClaim = claimsIdentity . Claims . FirstOrDefault ( x => x . Type == AccessTokenClaim ) ; if ( accessTokenClaim != null ) { await context . Response . WriteAsync ( \"Google access claims have persisted \\r \" ) ; }
- 确保已清除饼干,运行应用程序并浏览。
- 这有多安全?您能从cookie中弄清楚访问令牌的详细信息是什么?
- 让我们对Google发送的东西做一些有趣的事情。
- 返回到Google API仪表板,确保选择您的项目,然后单击“凭据”。
- 下拉创建凭据下拉并选择API密钥。复制您在安全的地方给出的价值。
- 用以下内容替换
app.Run()lambda,在适当的位置添加您的API键;
app . UseHttpsRedirection ( ) ; app . UseAuthentication ( ) ; app . Run ( async ( context ) => { if ( ! context . User . Identity . IsAuthenticated ) { await context . ChallengeAsync ( ) ; } context . Response . Headers . Add ( \"Content-Type\" , \"text/html\" ) ; await context . Response . WriteAsync ( \"<html><body> \\r \" ) ; var claimsIdentity = ( ClaimsIdentity ) context . User . Identity ; var nameIdentifier = claimsIdentity . Claims . FirstOrDefault ( x => x . Type == ClaimTypes . NameIdentifier ) . Value ; var googleApiKey = \"**API KEY**\" ; if ( ! string . IsNullOrEmpty ( nameIdentifier ) ) { string jsonUrl = $ \"https://www.go*og**leapis.com/plus/v1/people/ { nameIdentifier } ?fields=image&key= { googleApiKey } \" ; using ( var httpClient = new HttpClient ( ) ) { var s = await httpClient . GetStringAsync ( jsonUrl ) ; dynamic deserializeObject = JsonConvert . DeserializeObject ( s ) ; var thumbnailUrl = ( string ) deserializeObject . image . url ; if ( thumbnailUrl != null && ! string . IsNullOrWhiteSpace ( thumbnailUrl ) ) { await context . Response . WriteAsync ( string . Format ( $ \"<img src= \\\" { thumbnailUrl } \\\" ></img>\" ) ) ; } } } await context . Response . WriteAsync ( \"</body></html> \\r \" ) ; } ) ; } } }
- 重新运行应用程序,并查看您的Google个人资料图像的精彩。
步骤3:方案,动词
-
这一切如何悬挂在一起?
- 为什么Google身份验证也需要Cookie身份验证?
- 什么是计划?
-
远程身份验证就是这样。偏僻的。没有持续的机制可以重复使用。
-
持续的身份验证是身份验证,其信息是随着每个请求发送的,例如基本身份验证,摘要身份验证,证书身份验证或基于cookie的令牌。
-
检查
ConfigureServices()中的身份验证配置
services . AddAuthentication ( options => { options . DefaultAuthenticateScheme = CookieAuthenticationDefaults . AuthenticationScheme ; options . DefaultSignInScheme = CookieAuthenticationDefaults . AuthenticationScheme ; options . DefaultChallengeScheme = GoogleDefaults . AuthenticationScheme ; } )
-
AuthenticationService配置上有多个Default*选项;- DefaultAuthenticatesCheme
- Defaultchallengescheme
- Defaultforbidscheme
- 默认设备
- DefaultSignInscheme
- DefaultSignoutscheme
- 当您拥有多种身份验证类型时,您会使用这些配置设置来决定哪种身份验证类型负责什么
- 一切都基于计划。一个方案只是一个字符串,您将在配置时提供身份验证提供商。
-
DefaultScheme,默认值是默认值。除非您覆盖其他事件,否则它提供每个活动处理程序。 -
AuthenticationScheme是在每个请求上运行的提供商,并试图从请求中的信息中构建身份。 -
ChallengeScheme是将应对挑战的提供商,即需要授权时发生的事件,并且请求没有身份。 -
ForbidScheme是处理禁止事件的提供商,当授权发生时会起火,并且当前身份失败了授权检查。 -
DefaultSignInScheme和DefaultSignOutScheme指示提供者将处理SignIn和SignOut调用。
-
- 那么您的应用程序中的配置会做什么?会有所不同吗?
步骤4:配置cookie身份验证
-
启动您的浏览器,查看已发出的asp.net cookie,\’aspnetcore.cookies\’
-
饼干包含什么?
-
饼干的到期是什么?
-
您认为饼干如何受到保护?
-
让我们配置那个cookie;首先,让我们在其上设置一个永久到期日期,以便在浏览器上持续关闭
. AddCookie ( options => { options . Cookie . Expiration = new System . TimeSpan ( 0 , 15 , 0 ) ; } )
- Cookie Builder中还有哪些其他选项?为什么还可以选择期权? (它要走了)
- 那滑动到期呢?滑动到期在饼干构建器之外;请注意,我们需要从cookie构建器中删除到期时间板
. AddCookie ( options => { options . ExpireTimeSpan = new System . TimeSpan ( 0 , 15 , 0 ) ; options . SlidingExpiration = true ; } )
- 为什么我不能在选项中设置Cookie Builder中的到期? (Cookie Authentication服务有其自己的设置以支持滑动到期,还可以将到期嵌入到Cookie值本身中。为什么它将其嵌入值?)
- 将cookie从会话cookie更改为持续的cookie后,您现在会注意到您不会再次通过Google身份验证反弹,cookie身份验证是真实的单一来源。那么,如果您从数据库中填充cookie并且值会发生变化,该怎么办?为此,我们举行了
OnValidatePrincipal事件。 - 在您的startup.cs中创建验证器
private static int RequestCount = 0 ; public static async Task ValidateAsync ( CookieValidatePrincipalContext context ) { if ( context . Request . Path . HasValue && context . Request . Path == \"/\" ) { System . Threading . Interlocked . Increment ( ref RequestCount ) ; } if ( RequestCount % 5 == 0 ) { context . RejectPrincipal ( ) ; await context . HttpContext . SignOutAsync ( CookieAuthenticationDefaults . AuthenticationScheme ) ; } }
- 每5个请求 /将拒绝当前本金并触发登录。如果您只想更改校长,则可以使用
context.ReplacePrincipal(newPrincipal);
context.ShouldRenew = true;
- 现在,让我们谈谈如何保护饼干,因为该值不是纯文本。
- ASP.NET Core具有数据保护服务,该服务可创建和旋转整个堆栈中使用的密钥。
- 数据保护有两个概念,即密钥持久性和关键保护。
public void ConfigureServices ( IServiceCollection services ) { services . AddDataProtection ( ) . PersistKeysToFileSystem ( new DirectoryInfo ( @\"\\\\server\\share\\directory\\\" ) ) . ProtectKeysWithCertificate ( \"thumbprint\" ) ; }
- 在某些环境中,我们可以自动弄清楚该做什么。检查您的启动日志以查看我们要做什么。
- Linux和MacOS需要特定的选择。
- 默认情况下,应用程序获得隔离,共享cookie共享键盘并设置静态应用程序名称
public void ConfigureServices ( IServiceCollection services ) { services . AddDataProtection ( ) . SetApplicationName ( \"shared app name\" ) ; }
步骤5:声称转型
- 索赔转换使您可以添加,删除甚至替换在
AuthenticateAsync()中构建的主体。 - 索赔转换是一项服务,实施了
IClaimsTransformation。 - 让我们写一个在身份验证过程中向资源添加索赔的一个,而不必使用特定于身份验证的事件。
class ClaimsTransformer : IClaimsTransformation { public Task < ClaimsPrincipal > TransformAsync ( ClaimsPrincipal principal ) { ( ( ClaimsIdentity ) principal . Identity ) . AddClaim ( new Claim ( \"transformedOn\" , DateTime . Now . ToString ( ) ) ) ; return Task . FromResult
