.net core 认证

认证、登录与注销

认证是一个旨在确定请求访问者真实身份的过程,与认证相关的还有其他两个基本操作——登录和注销。要真正理解认证、登录和注销这 3个核心操作的本质,就需要对 ASP.NET Core采用的基于“票据”的认证机制有基本的了解。

认证票据

ASP.NET Core应用的认证实现在一个名为AuthenticationMiddleware的中间件中,该中间件在处理分发给它的请求时会按照指定的认证方案(Authentication Scheme)从请求中提取能够验证用户真实身份的数据,我们一般将该数据称为安全令牌(Security Token)。ASP.NET Core 应用下的安全令牌被称为认证票据(Authentication Ticket),所以 ASP.NET Core 应用采用基于票据的认证方式。

3种针对认证票据的操作,即认证票据的颁发、检验和撤销。我们将这 3 个操作所涉及的 3 种角色称为票据颁发者(Ticket Issuer)验证者(Authenticator)撤销者(Ticket Revoker),在大部分场景下这 3种角色由同一个主体来扮演。

颁发认证票据的过程就是登录(Sign In)操作。一般来说,用户试图通过登录应用以获取认证票据的时候需要提供可用来证明自身身份的用户凭证(User Credential),最常见的用户凭证类型是“用户名+密码”。认证方在确定对方真实身份之后,会颁发一个认证票据,该票据携带着与该用户有关的身份、权限及其他相关的信息。
一旦拥有了由认证方颁发的认证票据,我们就可以按照双方协商的方式(如通过 Cookie或者报头)在请求中携带该认证票据,并以此票据声明的身份执行目标操作或者访问目标资源。认证票据一般都具有时效性,一旦过期将变得无效。我们有的时候甚至希望在过期之前就让认证票据无效,以免别人使用它冒用自己的身份与应用进行交互,这就是注销(Sign Out)操作。
ASP.NET Core 的认证系统旨在构建一个标准的模型,用来完成针对请求的认证以及与之相关的登录和注销操作。

 

如下面的代码片段所示,我们调用IApplicationBuilder接口的UseAuthentication扩展方法就是为了注册用来实现认证的 AuthenticationMiddleware 中间件。该中间件的依赖服务是通过调用IServiceCollection 接口的 AddAuthentication 扩展方法注册的。在注册这些基础服务时,我们还设置了默认采用的认证方案,静态类型 CookieAuthenticationDefaults的 AuthenticationScheme属性返回的就是Cookie认证方案的默认方案名称。

ASP.NET Core 提供了一个极具扩展性的认证模型,我们可以利用它支持多种认证方案,对认证方案的注册是通过AddAuthentication方法返回的一个AuthenticationBuilder对象来实现的。在上面提供的代码片段中,我们调用AuthenticationBuilder对象的AddCookie扩展方法完成了针对Cookie认证方案的注册。演示实例的主页是通过如下所示的 RenderHomePageAsync 方法来呈现的。由于我们要求浏览主页必须是经过认证的用户,所以该方法会利用 HttpContext 上下文的 User 属性返回的ClaimsPrincipal对象判断当前请求是否经过认证。对于经过认证的请求,我们会响应一个简单的HTML文档,并在其中显示用户名和一个注销链接。

对于匿名请求,我们希望应用能够自动重定向到登录路径。从如上所示的代码片段可以看出,我们仅仅调用当前HttpContext上下文的ChallengeAsync扩展方法就完成了针对登录路径的重定向。前面提及,注册的登录和注销路径是基于 Cookie的认证方案采用的默认路径,所以调用 ChallengeAsync方法时根本不需要指定重定向路径。

 

如下所示的代码片段是用于处理登录请求的 SignInAsync方法的定义,而 RenderLoginPage Async方法用来呈现登录页面。如下面的代码片段所示,对于GET请求,SignInAsync方法会直接调用 RenderLoginPageAsync方法来呈现登录界面。对于 POST请求,我们会从提交的表单中提取用户名和密码,并对其实施验证。如果提供的用户名与密码一致,我们会根据用户名创建一个代表身份的GenericIdentity对象,并利用它创建一个代表登录用户的ClaimsPrincipal对象,RenderHomeAsync方法正是利用该对象来检验当前用户是否是经过认证的。有了 Claims Principal对象,我们只需要将它作为参数调用 HttpContext上下文的 SignInAsync扩展方法即可完成登录,该方法最终会自动重定向到初始方法的路径,也就是我们的主页。

 

一个细节,调用 HttpContext上下文的 ChallengeAsync方法会将当前路径(主页路径“/”,经过编码后为“%2F”)存储在一个名为 ReturnUrl的查询字符串中,SignInAsync方法正是利用它实现对初始路径的重定向的。
既然登录可以通过调用当前 HttpContext上下文的 SignInAsync扩展方法来完成,那么注销操作对应的自然就是 SignOutAsync扩展方法。如下面的代码片段所示,我们定义在 Program中的 SignOutAsync扩展方法正是调用这个方法来注销当前登录状态的。我们在完成注销之后将应用重定向到主页。

 

 

 

  1 using ConsoleApp7;
  2 using Microsoft.AspNetCore.Authentication;
  3 using Microsoft.AspNetCore.Authentication.Cookies;
  4 using Microsoft.AspNetCore.Builder;
  5 using Microsoft.AspNetCore.Hosting;
  6 using Microsoft.Extensions.Caching.Distributed;
  7 using Microsoft.Extensions.Caching.Memory;
  8 using Microsoft.Extensions.Caching.Redis;
  9 using Microsoft.Extensions.FileProviders;
 10 using Microsoft.Extensions.FileSystemGlobbing;
 11 using Microsoft.Extensions.Options;
 12 using System.Diagnostics;
 13 using System.Net;
 14 using System.Security.Claims;
 15 using System.Security.Principal;
 16 using System.Text;
 17 Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webHost =>
 18 {
 19     webHost.ConfigureServices(services => { });
 20     webHost.Configure(config => { });
 21     webHost.UseStartup<StartUp>();
 22 }).Build().Run();
 23 
 24 public class StartUp
 25 {
 26     public void ConfigureServices(IServiceCollection services)
 27     {
 28         //注册路由
 29         services.AddRouting();
 30         //注册认证
 31         services.AddAuthentication(configureOptions =>
 32         {
 33             //设置默认认证方案
 34             configureOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
 35         }).AddCookie(configureOptions =>
 36         {
 37             //默认是/Account/Login 可以自定义配置
 38             configureOptions.LoginPath = "/baidu/Login";
 39             configureOptions.LogoutPath = "/baidu/Logout";
 40         });
 41     }
 42     public void Configure(IApplicationBuilder builder)
 43     {
 44         //使用认证
 45         builder.UseAuthentication();
 46         //使用路由
 47         builder.UseRouting();
 48         //使用终结点
 49         builder.UseEndpoints(endPoints =>
 50         {
 51             endPoints.Map("/", ServerSign.RenderHomeAsync);
 52             //默认是 Account 可以自由设置 baidu
 53             endPoints.Map("/baidu/Login", ServerSign.SignIn);
 54             endPoints.Map("/baidu/LoginOut", ServerSign.SignOut);
 55         });
 56     }
 57 }
 58 
 59 public class ServerSign
 60 {
 61     public static async Task RenderHomeAsync(HttpContext httpContext)
 62     {
 63         if (httpContext?.User?.Identity?.IsAuthenticated == true)
 64         {
 65             httpContext.Response.ContentType = "text/html;charset=utf-8";
 66             string html = "<html><body>";
 67             html += "<p>认证通过</p>";
 68             html += "<a href='/baidu/LoginOut'>登出</p>";
 69             html += "</body></html>";
 70             await httpContext.Response.WriteAsync(html);
 71         }
 72         else
 73         {
 74             // 默认认证未通过跳转
 75             await httpContext.ChallengeAsync();
 76         }
 77     }
 78 
 79     public static async void SignIn(HttpContext httpContext)
 80     {
 81         //get请求
 82         if (httpContext.Request.Method.ToLower() == "get")
 83         {
 84             httpContext.Response.ContentType = "text/html;charset=utf-8";
 85             string html = "<html><body>";
 86             html += "<p>请登陆</p>";
 87             html += "<form method='post'> <p><label>用户名称:</label><input type='text' name='name' id ='name'></p>";
 88             html += "<input type='hidden'></input>";
 89             html += "<p><label>密码:</label><input type='password' name='password' id ='password'></p>";
 90             html += "<input type='submit'>登陆 </input> </form>";
 91             html += "</body></html>";
 92             await httpContext.Response.WriteAsync(html);
 93         }
 94         else
 95         {
 96             //post请求
 97             var name = httpContext.Request.Form["name"];
 98             var password = httpContext.Request.Form["password"];
 99             if (name.Equals("1") && password.Equals("1"))
100             {
101                 var identity = new GenericIdentity(name, "password");
102                 await httpContext.SignInAsync(new ClaimsPrincipal(identity));
103             }
104             else
105             {
106                 await RenderHomeAsync(httpContext);
107             }
108         }
109     }
110     public static async void SignOut(HttpContext httpContext)
111     {
112         //httpContext.Response.ContentType = "text/plain;charset=utf-8";
113         //httpContext.Response.WriteAsync("登出");
114         await httpContext.SignOutAsync();
115         httpContext.Response.Redirect("/");
116     }
117 }

 

 

测试结果
登陆成功跳转

 


登出 跳转


 

登出跳转

 

 

 

 

 

 




CookieAuthenticationDefaults 源码

 

47 // Licensed to the .NET Foundation under one or more agreements.
48 // The .NET Foundation licenses this file to you under the MIT license.
49  
50 using Microsoft.AspNetCore.Http;
51  
52 namespace Microsoft.AspNetCore.Authentication.Cookies;
53  
54 /// <summary>
55 /// Default values related to cookie-based authentication handler
56 /// </summary>
57 public static class CookieAuthenticationDefaults
58 {
59     /// <summary>
60     /// The default value used for CookieAuthenticationOptions.AuthenticationScheme
61     /// </summary>
62     public const string AuthenticationScheme = "Cookies";
63  
64     /// <summary>
65     /// The prefix used to provide a default CookieAuthenticationOptions.CookieName
66     /// </summary>
67     public static readonly string CookiePrefix = ".AspNetCore.";
68  
69     /// <summary>
70     /// The default value used by CookieAuthenticationMiddleware for the
71     /// CookieAuthenticationOptions.LoginPath
72     /// </summary>
73     public static readonly PathString LoginPath = new PathString("/Account/Login");
74  
75     /// <summary>
76     /// The default value used by CookieAuthenticationMiddleware for the
77     /// CookieAuthenticationOptions.LogoutPath
78     /// </summary>
79     public static readonly PathString LogoutPath = new PathString("/Account/Logout");
80  
81     /// <summary>
82     /// The default value used by CookieAuthenticationMiddleware for the
83     /// CookieAuthenticationOptions.AccessDeniedPath
84     /// </summary>
85     public static readonly PathString AccessDeniedPath = new PathString("/Account/AccessDenied");
86  
87     /// <summary>
88     /// The default value of the CookieAuthenticationOptions.ReturnUrlParameter
89     /// </summary>
90     public static readonly string ReturnUrlParameter = "ReturnUrl";
91 }

 

UseAuthentication 源码


 75 // Licensed to the .NET Foundation under one or more agreements.
 76 // The .NET Foundation licenses this file to you under the MIT license.
 77  
 78 using Microsoft.AspNetCore.Http;
 79 using Microsoft.AspNetCore.Http.Features.Authentication;
 80 using Microsoft.Extensions.DependencyInjection;
 81  
 82 namespace Microsoft.AspNetCore.Authentication;
 83  
 84 /// <summary>
 85 /// Middleware that performs authentication.
 86 /// </summary>
 87 public class AuthenticationMiddleware
 88 {
 89     private readonly RequestDelegate _next;
 90  
 91     /// <summary>
 92     /// Initializes a new instance of <see cref="AuthenticationMiddleware"/>.
 93     /// </summary>
 94     /// <param name="next">The next item in the middleware pipeline.</param>
 95     /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
 96     public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
 97     {
 98         ArgumentNullException.ThrowIfNull(next);
 99         ArgumentNullException.ThrowIfNull(schemes);
100  
101         _next = next;
102         Schemes = schemes;
103     }
104  
105     /// <summary>
106     /// Gets or sets the <see cref="IAuthenticationSchemeProvider"/>.
107     /// </summary>
108     public IAuthenticationSchemeProvider Schemes { get; set; }
109  
110     /// <summary>
111     /// Invokes the middleware performing authentication.
112     /// </summary>
113     /// <param name="context">The <see cref="HttpContext"/>.</param>
114     public async Task Invoke(HttpContext context)
115     {
116         context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
117         {
118             OriginalPath = context.Request.Path,
119             OriginalPathBase = context.Request.PathBase
120         });
121  
122         // Give any IAuthenticationRequestHandler schemes a chance to handle the request
123         var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
124         foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
125         {
126             var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
127             if (handler != null && await handler.HandleRequestAsync())
128             {
129                 return;
130             }
131         }
132  
133         var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
134         if (defaultAuthenticate != null)
135         {
136             var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
137             if (result?.Principal != null)
138             {
139                 context.User = result.Principal;
140             }
141             if (result?.Succeeded ?? false)
142             {
143                 var authFeatures = new AuthenticationFeatures(result);
144                 context.Features.Set<IHttpAuthenticationFeature>(authFeatures);
145                 context.Features.Set<IAuthenticateResultFeature>(authFeatures);
146             }
147         }
148  
149         await _next(context);
150     }
151 }
CookieAuthenticationOptions 源码
  1 File: CookieAuthenticationOptions.cs
  2 Web Access
  3 Project: src\src\Security\Authentication\Cookies\src\Microsoft.AspNetCore.Authentication.Cookies.csproj (Microsoft.AspNetCore.Authentication.Cookies)
141 // Licensed to the .NET Foundation under one or more agreements.
142 // The .NET Foundation licenses this file to you under the MIT license.
143  
144 using Microsoft.AspNetCore.DataProtection;
145 using Microsoft.AspNetCore.Http;
146  
147 namespace Microsoft.AspNetCore.Authentication.Cookies;
148  
149 /// <summary>
150 /// Configuration options for <see cref="CookieAuthenticationOptions"/>.
151 /// </summary>
152 public class CookieAuthenticationOptions : AuthenticationSchemeOptions
153 {
154     private CookieBuilder _cookieBuilder = new RequestPathBaseCookieBuilder
155     {
156         // the default name is configured in PostConfigureCookieAuthenticationOptions
157  
158         // To support OAuth authentication, a lax mode is required, see https://github.com/aspnet/Security/issues/1231.
159         SameSite = SameSiteMode.Lax,
160         HttpOnly = true,
161         SecurePolicy = CookieSecurePolicy.SameAsRequest,
162         IsEssential = true,
163     };
164  
165     /// <summary>
166     /// Create an instance of the options initialized with the default values
167     /// </summary>
168     public CookieAuthenticationOptions()
169     {
170         ExpireTimeSpan = TimeSpan.FromDays(14);
171         ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
172         SlidingExpiration = true;
173         Events = new CookieAuthenticationEvents();
174     }
175  
176     /// <summary>
177     /// Determines the settings used to create the cookie.
178     /// </summary>
179     /// <remarks>
180     /// <list type="bullet">
181     /// <item><description>The default value for cookie <see cref="CookieBuilder.Name"/> is <c>.AspNetCore.Cookies</c>.
182     /// This value should be changed if you change the name of the <c>AuthenticationScheme</c>, especially if your
183     /// system uses the cookie authentication handler multiple times.</description></item>
184     /// <item><description><see cref="CookieBuilder.SameSite"/> determines if the browser should allow the cookie to be attached to same-site or cross-site requests.
185     /// The default is <c>Lax</c>, which means the cookie is only allowed to be attached to cross-site requests using safe HTTP methods and same-site requests.</description></item>
186     /// <item><description><see cref="CookieBuilder.HttpOnly"/> determines if the browser should allow the cookie to be accessed by client-side JavaScript.
187     /// The default is <c>true</c>, which means the cookie will only be passed to HTTP requests and is not made available to JavaScript on the page.</description></item>
188     /// <item><description><see cref="CookieBuilder.Expiration"/> is currently ignored. Use <see cref="ExpireTimeSpan"/> to control lifetime of cookie authentication.</description></item>
189     /// <item><description><see cref="CookieBuilder.SecurePolicy"/> defaults to <see cref="CookieSecurePolicy.SameAsRequest"/>.</description></item>
190     /// </list>
191     /// </remarks>
192     public CookieBuilder Cookie
193     {
194         get => _cookieBuilder;
195         set => _cookieBuilder = value ?? throw new ArgumentNullException(nameof(value));
196     }
197  
198     /// <summary>
199     /// If set this will be used by the CookieAuthenticationHandler for data protection.
200     /// </summary>
201     public IDataProtectionProvider? DataProtectionProvider { get; set; }
202  
203     /// <summary>
204     /// The SlidingExpiration is set to true to instruct the handler to re-issue a new cookie with a new
205     /// expiration time any time it processes a request which is more than halfway through the expiration window.
206     /// </summary>
207     public bool SlidingExpiration { get; set; }
208  
209     /// <summary>
210     /// The LoginPath property is used by the handler for the redirection target when handling ChallengeAsync.
211     /// The current url which is added to the LoginPath as a query string parameter named by the ReturnUrlParameter.
212     /// Once a request to the LoginPath grants a new SignIn identity, the ReturnUrlParameter value is used to redirect
213     /// the browser back to the original url.
214     /// </summary>
215     public PathString LoginPath { get; set; }
216  
217     /// <summary>
218     /// If the LogoutPath is provided the handler then a request to that path will redirect based on the ReturnUrlParameter.
219     /// </summary>
220     public PathString LogoutPath { get; set; }
221  
222     /// <summary>
223     /// The AccessDeniedPath property is used by the handler for the redirection target when handling ForbidAsync.
224     /// </summary>
225     public PathString AccessDeniedPath { get; set; }
226  
227     /// <summary>
228     /// The ReturnUrlParameter determines the name of the query string parameter which is appended by the handler
229     /// during a Challenge. This is also the query string parameter looked for when a request arrives on the login
230     /// path or logout path, in order to return to the original url after the action is performed.
231     /// </summary>
232     public string ReturnUrlParameter { get; set; }
233  
234     /// <summary>
235     /// The Provider may be assigned to an instance of an object created by the application at startup time. The handler
236     /// calls methods on the provider which give the application control at certain points where processing is occurring.
237     /// If it is not provided a default instance is supplied which does nothing when the methods are called.
238     /// </summary>
239     public new CookieAuthenticationEvents Events
240     {
241         get => (CookieAuthenticationEvents)base.Events!;
242         set => base.Events = value;
243     }
244  
245     /// <summary>
246     /// The TicketDataFormat is used to protect and unprotect the identity and other properties which are stored in the
247     /// cookie value. If not provided one will be created using <see cref="DataProtectionProvider"/>.
248     /// </summary>
249     public ISecureDataFormat<AuthenticationTicket> TicketDataFormat { get; set; } = default!;
250  
251     /// <summary>
252     /// The component used to get cookies from the request or set them on the response.
253     ///
254     /// ChunkingCookieManager will be used by default.
255     /// </summary>
256     public ICookieManager CookieManager { get; set; } = default!;
257  
258     /// <summary>
259     /// An optional container in which to store the identity across requests. When used, only a session identifier is sent
260     /// to the client. This can be used to mitigate potential problems with very large identities.
261     /// </summary>
262     public ITicketStore? SessionStore { get; set; }
263  
264     /// <summary>
265     /// <para>
266     /// Controls how much time the authentication ticket stored in the cookie will remain valid from the point it is created
267     /// The expiration information is stored in the protected cookie ticket. Because of that an expired cookie will be ignored
268     /// even if it is passed to the server after the browser should have purged it.
269     /// </para>
270     /// <para>
271     /// This is separate from the value of <see cref="CookieOptions.Expires"/>, which specifies
272     /// how long the browser will keep the cookie.
273     /// </para>
274     /// </summary>
275     public TimeSpan ExpireTimeSpan { get; set; }
276 }
277 Document OutlineProject ExplorerNamespace Explorer

 

posted on 2023-02-20 16:57  是水饺不是水饺  阅读(59)  评论(0编辑  收藏  举报

导航