写在前面

本文版权归博客园和作者吴双本人共同所有 转载和爬虫请注明原文地址 cnblogs.com/tdws

After using OWIN for months for basic OAuth authentication, it’s apparent that Microsoft is abandoning OWIN . This isn’t necessarily a bad thing. .NET Core is built on a similar structure as that which was implemented in OWIN. Essentially, we have a familiar middleware pipeline.

这句话出自老外的博客,在使用Owin的OAuth身份认证几个月后,发现微软在逐渐放弃OWIN,这未必是一件坏事情,.NET Core在一个和OWIN所实现的相似结构之上。我们有一个和OWIN极为相似的中间件管道。

   想必了解或者使用过OWIN的朋友们,在做.NET Core应用的时候都会有如上描述的这种感觉。就我个人的理解,微软在早几年推出OWIN的时候,就希望将管道留给用户,就以Startup.cs为管道配置和应用入口,OWIN脱离了Asp.Net管道事件,我们可以将任何中间件在管道中随意插拔。在OWIN中为我们提供了完备的认证流程,和一套完整的规范。比如 Microsoft.Owin.Security.OAuth等,在使用OWIN时,我们可以使用OWIN的默认实现,也可以实现其接口,自定义我们自己的实现方式。有关Microsoft OWIN的内容,不是本篇分享的主题,推荐腾飞的MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN 和蟋蟀哥的 ASP.NET WebApi OWIN 实现 OAuth 2.0  。

Token

 本篇分享主要关注在.NET Core的认证机制。无论我们是使用WebApi还是MvcWeb App,了解微软的认证机制总是有好处的。认证是应用API服务器识别用户身份的过程,token是更现代的认证方式,简化权限管理,降低服务器负载。在认证过程中,最重要的就是拿到token, token包含或者应该包含什么信息呢?

    1.这个人是谁?

    2.这个人可以用此token访问什么样的内容?(scope)

    3.token的过期时间 (expire)

    4.谁发行的token。

    5.其他任何你希望加入的声明(Claims)

 那我们为什么要使用token呢?使用session或者用redis来实现stateServer不好吗?

    1.token是低(无)状态的,Statelessness

    2.token可以与移动端应用紧密结合

    3.支持多平台服务器和分布式微服务

拿到token后如何带入HTTP请求传给后台?

 答案是两种方式,Cookies和Authorization Header。那么什么时候放到Cookies中,什么时候又放到Authentication中呢?

第一,如果是在Web应用,则放到Cookies当中,并且应该是HttpOnly的,js不能直接对其进行操作,安全性会比将其存在Web Stroage中好一些,因为在Web Storage当中的内容,可以很容的被潜在的XSS脚本攻击并获取。在HttpOnly的cookies当中会相对安全一些,不过也有潜在的CSRF跨站伪造请求的危险,不过这种hack的手段成功率是很低的,有兴趣的朋友可以自行看一下CSRF原理。

第二,如果是手机移动端应用的话,那一定是存储在App本地,并由Authorization Header带到后台并得到身份认证。

WebApp Cookies Authentication

上一段前两周写的最原始的小Demo吧,没有数据库访问等,可根据demo自行改变 ,现在的新代码已经加入了很多业务在其中

startup.cs代码

  1. using Microsoft.AspNetCore.Authentication.Cookies;
  2. using Microsoft.AspNetCore.Builder;
  3. using Microsoft.AspNetCore.Hosting;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Http.Authentication;
  6. using Microsoft.Extensions.Configuration;
  7. using Microsoft.Extensions.DependencyInjection;
  8. using Microsoft.Extensions.Logging;
  9. using System.Collections.Generic;
  10. using System.Security.Claims;
  11. using Wings.AuthenticationApp.Middleware;
  12.  
  13. namespace Wings.AuthenticationApp
  14. {
  15. public class Startup
  16. {
  17. public Startup(IHostingEnvironment env)
  18. {
  19. var builder = new ConfigurationBuilder()
  20. .SetBasePath(env.ContentRootPath)
  21. .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  22. .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
  23. .AddEnvironmentVariables();
  24. Configuration = builder.Build();
  25.  
  26. }
  27.  
  28. public IConfigurationRoot Configuration { get; }
  29.  
  30. // This method gets called by the runtime. Use this method to add services to the container.
  31. public void ConfigureServices(IServiceCollection services)
  32. {
  33. // Add framework services.
  34. services.AddMvc();
  35. }
  36.  
  37. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  38. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  39. {
  40. loggerFactory.AddConsole(Configuration.GetSection("Logging"));
  41. loggerFactory.AddDebug();
  42.  
  43. app.UseCookieAuthentication(CookieAuthMiddleware.GetOptions());
  44. app.UseOwin();
  45. app.UseCors(a => { a.AllowAnyOrigin(); });
  46. app.UseMvc();
  47. // Listen for login and logout requests
  48. app.Map("/login", builder =>
  49. {
  50. builder.Run(async context =>
  51. {
  52. var name = context.Request.Form["name"];
  53. var pwd = context.Request.Form["pwd"];
  54. if (name == "wushuang" && pwd == "wushuang")
  55. {
  56.  
  57. var claims = new List<Claim>() { new Claim("name", name), new Claim("role", "admin") };
  58. var identity = new ClaimsIdentity(claims, "password");
  59. var principal = new ClaimsPrincipal(identity);
  60. await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
  61. context.Response.Redirect("http://www.baidu.com");
  62. }
  63. else
  64. {
  65. await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
  66. context.Response.Redirect("http://www.google.com");
  67. }
  68. });
  69. });
  70.  
  71. //app.Map("/logout", builder =>
  72. //{
  73. // builder.Run(async context =>
  74. // {
  75. // // Sign the user out / clear the auth cookie
  76. // await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
  77.  
  78. // // Perform a simple redirect after logout
  79. // context.Response.Redirect("/");
  80. // });
  81. //});
  82.  
  83. }
  84.  
  85. }
  86. }

下面是Middleware---->CookieAuthMiddleware.cs的代码,

  1. using Microsoft.AspNetCore.Authentication.Cookies;
  2. using Microsoft.AspNetCore.Builder;
  3. using Microsoft.AspNetCore.Http;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Security.Claims;
  8. using System.Security.Principal;
  9. using System.Threading.Tasks;
  10.  
  11. namespace Wings.AuthenticationApp.Middleware
  12. {
  13. public class CookieAuthMiddleware
  14. {
  15. public static CookieAuthenticationOptions GetOptions()
  16. {
  17. return new CookieAuthenticationOptions
  18. {
  19. AutomaticAuthenticate = true,
  20. AutomaticChallenge = true,
  21. LoginPath = new PathString("/login"),
  22. LogoutPath = new PathString("/logout"),
  23. AccessDeniedPath = new PathString("/test"),
  24. CookieHttpOnly = false, //默认就是True了
  25. CookieName = "wings_access_token",
  26. SlidingExpiration = true,
  27. CookieManager = new ChunkingCookieManager()
  28. };
  29. }
  30. }
  31. public static class IdentityExtension
  32. {
  33. public static string FullName(this IIdentity identity)
  34. {
  35. var claim = ((ClaimsIdentity)identity).FindFirst("name");
  36. return (claim != null) ? claim.Value : string.Empty;
  37. }
  38. public static string Role(this IIdentity identity)
  39. {
  40. var claim = ((ClaimsIdentity)identity).FindFirst("role");
  41. return (claim != null) ? claim.Value : string.Empty;
  42. }
  43. }
  44. }

对应如上demo,简单测试一下,结果如下:

首先使用错误的密码,来请求token endpoint,接下来我们看一下即使窗口,当有请求进入的时候,我用如下代码判断用户的认证情况,拿到的结果必然是false:

接下来,我使用正确的账号密码,来打入token,判断结果一定为true,所以我使用自定义的拓展方法,来获取下,该用户token的信息:

如上demo没有加入一些容错机制,请注意。在用户认证成功后,可以进入带有Authorize Attribute的Action,否则401.如下是几个重要参数的解释

自定义Authentication Middle生产Token

Startup.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Builder;
  6. using Microsoft.AspNetCore.Hosting;
  7. using Microsoft.Extensions.Configuration;
  8. using Microsoft.Extensions.DependencyInjection;
  9. using Microsoft.Extensions.Logging;
  10. using Wings.TokenAuth.Middleware;
  11. using System.Security.Claims;
  12. using Microsoft.IdentityModel.Tokens;
  13. using System.Text;
  14. using Microsoft.Extensions.Options;
  15.  
  16. namespace Wings.TokenAuth
  17. {
  18. public class Startup
  19. {
  20. public Startup(IHostingEnvironment env)
  21. {
  22. var builder = new ConfigurationBuilder()
  23. .SetBasePath(env.ContentRootPath)
  24. .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  25. .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
  26. .AddEnvironmentVariables();
  27. Configuration = builder.Build();
  28. }
  29.  
  30. public IConfigurationRoot Configuration { get; }
  31.  
  32. // This method gets called by the runtime. Use this method to add services to the container.
  33. public void ConfigureServices(IServiceCollection services)
  34. {
  35. // Add framework services.
  36. services.AddMvc();
  37. }
  38.  
  39. // The secret key every token will be signed with.
  40. // In production, you should store this securely in environment variables
  41. // or a key management tool. Don't hardcode this into your application!
  42. private static readonly string secretKey = "mysupersecret_secretkey!123";
  43.  
  44. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  45. {
  46. loggerFactory.AddConsole(LogLevel.Debug);
  47. loggerFactory.AddDebug();
  48.  
  49. app.UseStaticFiles();
  50.  
  51. // Add JWT generation endpoint:
  52. var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
  53. var options = new TokenProviderOptions
  54. {
  55. Audience = "ExampleAudience",
  56. Issuer = "ExampleIssuer",
  57. SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
  58. };
  59.  
  60. app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));
  61.  
  62. app.UseMvc();
  63. }
  64. }
  65. }

TokenProviderOptions.cs

  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.Extensions.Options;
  3. using Microsoft.IdentityModel.Tokens;
  4. using Newtonsoft.Json;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IdentityModel.Tokens.Jwt;
  8. using System.Linq;
  9. using System.Security.Claims;
  10. using System.Threading.Tasks;
  11.  
  12. namespace Wings.TokenAuth.Middleware
  13. {
  14. public class TokenProviderOptions
  15. {
  16. public string Path { get; set; } = "/token";
  17.  
  18. public string Issuer { get; set; }
  19.  
  20. public string Audience { get; set; }
  21.  
  22. public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes();
  23.  
  24. public SigningCredentials SigningCredentials { get; set; }
  25. }
  26. public class TokenProviderMiddleware
  27. {
  28. private readonly RequestDelegate _next;
  29. private readonly TokenProviderOptions _options;
  30.  
  31. public TokenProviderMiddleware(
  32. RequestDelegate next,
  33. IOptions<TokenProviderOptions> options)
  34. {
  35. _next = next;
  36. _options = options.Value;
  37. }
  38.  
  39. public Task Invoke(HttpContext context)
  40. {
  41. // If the request path doesn't match, skip
  42. if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
  43. {
                 //use new JwtSecurityTokenHandler().ValidateToken() to valid token
  44. return _next(context);
  45. }
  46.  
  47. // Request must be POST with Content-Type: application/x-www-form-urlencoded
  48. if (!context.Request.Method.Equals("POST")
  49. || !context.Request.HasFormContentType)
  50. {
  51. context.Response.StatusCode = ;
  52. return context.Response.WriteAsync("Bad request.");
  53. }
  54.  
  55. return GenerateToken(context);
  56. }
  57. private async Task GenerateToken(HttpContext context)
  58. {
  59. var username = context.Request.Form["username"];
  60. var password = context.Request.Form["password"];
  61.  
  62. var identity = await GetIdentity(username, password);
  63. if (identity == null)
  64. {
  65. context.Response.StatusCode = ;
  66. await context.Response.WriteAsync("Invalid username or password.");
  67. return;
  68. }
  69.  
  70. var now = DateTime.UtcNow;
  71.  
  72. // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
  73. // You can add other claims here, if you want:
  74. var claims = new Claim[]
  75. {
  76. new Claim(JwtRegisteredClaimNames.Sub, username),
  77. new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
  78. new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64)
  79. };
  80.  
  81. // Create the JWT and write it to a string
  82. var jwt = new JwtSecurityToken(
  83. issuer: _options.Issuer,
  84. audience: _options.Audience,
  85. claims: claims,
  86. notBefore: now,
  87. expires: now.Add(_options.Expiration),
  88. signingCredentials: _options.SigningCredentials);
  89. var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
  90.  
  91. var response = new
  92. {
  93. access_token = encodedJwt,
  94. expires_in = (int)_options.Expiration.TotalSeconds
  95. };
  96.  
  97. // Serialize and return the response
  98. context.Response.ContentType = "application/json";
  99. await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
  100. }
  101.  
  102. private Task<ClaimsIdentity> GetIdentity(string username, string password)
  103. {
  104. // DON'T do this in production, obviously!
  105. if (username == "wushuang" && password == "wushuang")
  106. {
  107. return Task.FromResult(new ClaimsIdentity(new System.Security.Principal.GenericIdentity(username, "Token"), new Claim[] { }));
  108. }
  109.  
  110. // Credentials are invalid, or account doesn't exist
  111. return Task.FromResult<ClaimsIdentity>(null);
  112. }
  113.  
  114. public static long ToUnixEpochDate(DateTime date)
  115. => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(, , , , , , TimeSpan.Zero)).TotalSeconds);
  116.  
  117. }
  118. }

下面上测试结果:

使用错误的账户和密码请求token

使用正确的账户和密码来请求,返回结果如下:

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下加【推荐】按钮。
如果,您希望更容易地发现我的新博客,不妨点击下方红色【关注】的。
因为,我的分享热情也离不开您的肯定支持。

感谢您的阅读,我将持续输出分享,我是蜗牛, 保持学习,谨记谦虚。不端不装,有趣有梦。

参考文章和论文,不仅限于如下几篇,感谢国外大佬们有深度的分享:

http://stackoverflow.com/questions/29055477/oauth-authorization-service-in-asp-net-core

https://stormpath.com/blog/token-authentication-asp-net-core

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware#fundamentals-middleware

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie#controlling-cookie-options

https://stormpath.com/blog/token-authentication-asp-net-core

Asp.Net Core Authentication Middleware And Generate Token的更多相关文章

  1. ASP.NET Core Authentication系列(二)实现认证、登录和注销

    前言 在上一篇文章介绍ASP.NET Core Authentication的三个重要概念,分别是Claim, ClaimsIdentity, ClaimsPrincipal,以及claims-bas ...

  2. ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

    一.前言 在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore ...

  3. 在ASP.NET Core使用Middleware模拟Custom Error Page功能

    一.使用场景 在传统的ASP.NET MVC中,我们可以使用HandleErrorAttribute特性来具体指定如何处理Action抛出的异常.只要某个Action设置了HandleErrorAtt ...

  4. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  5. 利用Asp.Net Core的MiddleWare思想处理复杂业务流程

    最近利用Asp.Net Core 的MiddleWare思想对公司的古老代码进行重构,在这里把我的设计思路分享出来,希望对大家处理复杂的流程业务能有所帮助. 背景 一个流程初始化接口,接口中根据传入的 ...

  6. ASP.NET Core中Middleware的使用

    https://www.cnblogs.com/shenba/p/6361311.html   ASP.NET 5中Middleware的基本用法 在ASP.NET 5里面引入了OWIN的概念,大致意 ...

  7. [转]在ASP.NET Core使用Middleware模拟Custom Error Page功能

    本文转自:http://www.cnblogs.com/maxzhang1985/p/5974429.html 阅读目录 一.使用场景 二..NET Core实现 三.源代码 回到目录 一.使用场景 ...

  8. ASP.NET Core WebApi基于Redis实现Token接口安全认证

    一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebSer ...

  9. ASP.NET Core Authentication系列(四)基于Cookie实现多应用间单点登录(SSO)

    前言 本系列前三篇文章分别从ASP.NET Core认证的三个重要概念,到如何实现最简单的登录.注销和认证,再到如何配置Cookie 选项,来介绍如何使用ASP.NET Core认证.感兴趣的可以了解 ...

随机推荐

  1. CentOS7 部署 tomcat

    1. 准备tomcat账号 本着最小权限原则,新建账号来安装tomcat. 命令:useradd , passwd 2. 配置防火墙 2.1. tomcat.xml 在/etc/firewalld/s ...

  2. HDU1848-Fibonacci again and again

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1848 这个题目代码不是很复杂,但那个等价类,(SG函数)没怎么理解, 题目难,不过代码不怎么复杂,在网 ...

  3. IOS拒绝Adobe的六大理由

    苹果与Adobe的关系由来已久.事实上,Adobe的创始人还在他们众所周知的小车库里时,我们就碰过面了.苹果是他们的第一个大客户.我们将他们的Postscript语言应用于当时最新的Laserwrit ...

  4. 开源OSS.Social微信项目解析

    ​前言:OSS.Social是个开源的社交网站接口集成项目,当前也有很多其他不错的项目,不过始终没有我想要的那种简单清晰,只能撸起袖子,从头打造一个.当前正在进行的是对微信项目的开发,这里把对接口的整 ...

  5. 《JAVASCRIPT高级程序设计》第一章

    在使用调制解调器的时代,频繁的表单验证对客户端来说是一个很大的负担,javascript,作为一种专门进行表单验证的客户端脚本语言诞生了.到今天,javascript早已超越了当初设定的角色.Java ...

  6. HUST 1586 数字排列

    1586 - 数字排列 时间限制:1秒 内存限制:128兆 91 次提交 36 次通过 题目描述 现有n个k位的数字,你的任务是重新安排数字每一位的位置,使得重新安排后这n个数字中最大的数字和最小的数 ...

  7. CSS Flexbox 学习指南、工具与框架

    Flexbox 是一种更有效的布局方式,它能更好的分配容器空间,并控制项目的对齐.虽然,掌握它的理论有些复杂,但幸运的是,我们可以借助开放的网络来学习并逐步掌握它. 在本文中,我们整合了一些最佳的 F ...

  8. node-webkit制作桌面应用

    心血来潮突然想用js尝试写桌面应用,突然发现我大js真的无所不能.在网上搜到了这么一个东东:node-webkit.用Node.js来进行系统资源的访问,用HTML+CSS完成页面的搭建.哇,一切突然 ...

  9. SQLServer 数据的导入

    选择数据源,选择文件路径 直接点击下一步,选择自己的源表和目标表 勾选忽略,然后到完成,数据上传成功 可能出现的问题:源文件和目标文件的列名不一致,导致被忽略不能导入 预览发现有乱码,因为编码格式

  10. iOS开发——设计模式那点事

    单例模式(Singleton) 概念:整个应用或系统只能有该类的一个实例 在iOS开发我们经常碰到只需要某类一个实例的情况,最常见的莫过于对硬件参数的访问类,比如UIAccelerometer.这个类 ...