原文出自 Rui Figueiredo 的博客,原文链接 《Secure a Web Api in ASP.NET Core》
摘要:这边文章阐述了如何使用 Json web Token (Jwt) 方式 来配置身份验证中间件。这种方式十分适合移动 App 后端等不使用 cookie 的后端程序。
网络上有许多资源可以教你如何保护 ASP.NET Core Web 应用程序。我写过一些,例如 ASP.NET Core Identity From Scratch , External Login Providers in ASP.NET Core and Facebook Authentiation with ASP.NET Core.
不过对于保护 Asp.Net WebApi,网络上有用的信息似乎不多。所以在这篇博文中,我将介绍如何使用 Json Web Tokens(JWT)来保护 ASP.NET Core 中的 Web Api。我在 github 中有一个 演示项目 ,你可以照着它来做。
在一个 Web 应用程序中,如果你不打算使用供应外部调用(例如一个移动应用程序)的 API,那么它通常使用一个 cookie 来表示一个已经登录的用户。
一般的流程是:用户单击登录,进入登录页面,输入有效凭证后,服务器发送给用户浏览器的响应包含一个带有加密信息的 Set-Cookie 头。
cookie 会被设置上 domain 例如 blinkingcaret.com, 每次浏览器向这个 domain 发送请求时,设置在这个 domain 上的 cookie 也会被带上。
在服务器上,cookie 将被解密,然后使用解密后的内容来创建用户的 Identity。
如果客户端是一个浏览器,这种方式将会非常非常适合。不过当我们的客户端是一个移动应用程序时候,那就另当别论了。
我们可以使用什么来代替 cookie 呢?没错就是 token。token 也代表用户,但是当我们使用它的时候,我们不再依赖于浏览器的内置机制以及用它和 cookie 打交道。
我们必须明确地向服务器要一个 token,我们自己将它存储在某个地方,然后在每个请求发送时手动带上它。有一些方法可以使这个尽可能简单快捷,我会在后面讨论其中的一些方法。
我将在这里讨论的 token 格式是 JWT 。
JWT 代表 Json Web Token。JWTtoken 具有以下格式
。
- base64-encoded-header.base64-encoded-payload.signature
一个 heder 的例子是
- {
- "alg": "HS265",
- "typ": "JWT"
- }
payload 包含一系列 claims ,例如:
- {
- "name": "Rui",
- "admin": true
- }
最后,通过采用 "base64(header).base64(payload)" 创建签名,并使用头部指定的算法对签名其进行加密。例如 HMAC-SHA256 。签名部分会用到一个存储在 server 上的密钥,这个密钥是不会发给客户端的。
下面是一个真正的 JWT 的例子:
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoicnVpIiwic3ViIjoidGVzdCIsIm5iZiI6MTUwMzYxNDU4NSwiZXhwIjoxNTA2MDMzNzg1LCJpc3MiOiJibGlua2luZ2NhcmV0IHN0cyIsImF1ZCI6ImJsaW5raW5nY2FyZXQgYXBwIn0.F7PFoYcQXez3zV98BFKLpyON6d_1p - 6IAeihZRSv0VM
你必须注意的是,JWT 中包含的信息没有加密。为了获得有效 payload,你只需要 base64 解码。你甚至可以从你的开发者工具控制台(例如在 Chrome 中)这样做。使用 atob 方法并将 payload 作为参数传递。你会得到解密后的 JSON 。signature 只能保证如果有人篡改了 payload,那么 signature 将会失效。如果有人想成功替换有效载荷并生成有效的 token,他们需要知道签名中使用的密钥,但是该密钥永远不会被发送到客户端。
所以,当你想往 payload 里放一些东西的时候,你一定要知道上面这些
译者注:就是不要把敏感信息放在 payload 里,比如:密码。
要在 ASP.NET Core 中使用 JWT,我们需要知道如何手动创建 JWTtoken,如何验证它们以及如何创建端点以便客户端应用程序可以获得它们。
首先你需要安装 nuget 包
:
- System.IdentityModel.Tokens.Jwt
- $ dotnet add package System.IdentityModel.Tokens.Jwt
然后创建一个密钥。我们将使用 symmetric key (译者注:对称密钥),代码如下:
- var secretKey = new SymmetricSecurityKey(Endoding.UTF8.GetBytes("a secret that needs to be at least 16 characters long"));
译者注:a secret that needs to be at least 16 characters long=> 一个至少需要 16 个字符的密码,在验证签名时还会用到。
我们的 token 将包含一组 claims。所以让我们创建它们:
- var claims = new Claim[] {
- new Claim(ClaimTypes.Name, "John"),
- new Claims(JwtRegisteredClaimNames.Email, "john.doe@blinkingcaret.com")
- }
我已经使用了两种 claim 类型 :
要强调的是 JwtRegisteredClaimNames 包含在 JWT RFC 中列举的 claims 中。如果你打算使用不同编程语言或者框架生成的 token,那么为了兼容性,你应该尽可能的使用这个。不过,有一些声明类型可以在 ASP.NET 中启用某些功能。例如,ClaimTypes.Name 是用户名(User.Identity.Name)的默认声明类型。另一个例子是 ClaimTypes.Role,如果你在 Authorize 属性中使用 Roles 属性(例如 [Authorize(Roles ="Administrator")]),这个声明将会被检查用来确认权限。
在创建我们想要在 token 中编码的 claims 列表之后,我们可以创建 token 本身,代码如下:
- var token = new JwtSecurityToken(
- issuer: "your app",
- audience: "the client of your app",
- claims: claims,
- notBefore: DateTime.Now,
- expires: DateTime.Now.AddDays(28),
- signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
- );
这里有一些我之前没有提到的概念,即发 issue,audience 和 expiration dates。
译者注: 发行者,受众 / 听众,过期时间
发行者表示生成 token 的实体,在这个例子里它是 ASP.NET Core Web 应用程序。audience 代表将要使用这些 token 的实体,例如 client。如果你依靠第三方创建 token(不是现在所要用到的),这个 issue 和 audience 是重要的。验证 token 时,你可以验证 issue 和 audience。
notBefore 和 expire 定义了 token 的有效时间区间,在 notBefore 之后 expire 之前。
最后在 signedCredentials 中指定使用哪个安全密钥和什么算法来创建签名。在这个例子中我们使用了 HMAC-SHA256 。
如果你不关心 issue 和 audience(在 JWT 规范中是可选的),你可以使用接受 JwtSecurityHeader 和 JwtSecurityPayload 的 JwtSecurityToken 的更简单的构造函数重载。不过你必须手动将 expires 和 notBefore 声明添加到有效内容中,例如:
- var claims = new Claim[] {
- new Claim(ClaimTypes.Name, "John"),
- new Claims(JwtRegisteredClaimNames.Email, "john.doe@blinkingcaret.com"),
- new Claim(JwtRegisteredClaimNames.Exp, $ "{new DateTimeOffset(DateTime.Now.AddDays(1)).ToUnixTimeSeconds()}"),
- new Claim(JwtRegisteredClaimNames.Nbf, $ "{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")
- }
- var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));
请注意 Exp(expires)和 Nbf(notBefore)声明的值是一个 Unix 时间 的字符串。将 DateTime 转换为该格式的最简单方法是使用 DateTimeOffset。
在创建 JwtSecurityToken 的实例后,实际生成 token 的方法是调用 JwtSecurityTokenHandler 实例的 WriteToken 方法,并将 JwtSecurityToken 作为参数传递:
- string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
现在我们知道如何创建我们的 JWT token 了,我们还需要一种方法来让客户端获得它们。最简单的方法是创建一个期望发布请求的 web api controller action 接受一个 Post 请求,例如下面的代码:
- public class TokenController : Controller
- {
- [Route("/token")]
- [HttpPost]
- public IActionResult Create(string username, string password)
- {
- if (IsValidUserAndPasswordCombination(username, password))
- return new ObjectResult(GenerateToken(username));
- return BadRequest();
- }
- //...
在
中,你可以来验证用户的凭据例如使用例如 ASP.NET Identity(如果你需要参考资料来学习 ASP.NET Identity,你可以看这篇博客 ASP.NET Identity Core From Scratch )。
- IsValidUserAndPasswordCombination
GenerateToken 我们刚刚在上一节中描述过。
现在我们有了一种发行 token 的方法,我们还需要一种方法来验证它们。我们将使用 ASP.NET Core 的身份验证中间件,并将其配置为可接受 JWT token。
将
NuGet 包添加到你的项目。
- Microsoft.AspNetCore.Authentication.JwtBearer
- $ dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
接下来打开 Startup.cs 并更新 ConfigureServices 方法:
- public void ConfigureServices(IServiceCollection services)
- {
- //...
- services.AddAuthentication(options => {
- options.DefaultAuthenticateScheme = "JwtBearer";
- options.DefaultChallengeScheme = "JwtBearer";
- })
- .AddJwtBearer("JwtBearer", jwtBearerOptions =>
- {
- jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your secret goes here")),
- ValidateIssuer = true,
- ValidIssuer = "The name of the issuer",
- ValidateAudience = true,
- ValidAudience = "The name of the audience",
- ValidateLifetime = true, //validate the expiration and not before values in the token
- ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date
- };
- });
- }
如果你不熟悉 ASP.NET Core 的身份验证中间件,则建议你阅读 External Login Providers in ASP.NET Core 。
即使是关于如何使用 Google,Facebook 等进行外部登陆提供程序登录,但是这篇博客也含有有关身份验证中间件如何工作的详细说明。
此外请注意,这是新的 ASP.NET Core 2.0 语法,其中通过 ConfigureServices 方法完全配置了身份验证,但概念是相同的。
译者注: External Login Providers in ASP.NET Core 这篇博客在撰写的时候使用的是 Asp.Net Core 1.x。
在这个例子中更重要的是
类。这是你必须实例化的类,它将用来配置如何验证 token。
- TokenValidationParameters
在 Startup.cs 中,你需要更新 Configure 方法并添加身份验证中间件:
- public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
- //...
- app.UseAuthentication(); //needs to be up in the pipeline, before MVC
- //...
- app.UseMvc(ConfigureRoutes);
- //..
web api 客户端可以是桌面应用程序,移动设备甚至是浏览器。我将要描述的例子是 Web 应用程序的登录、保存 token、然后使用它来执行对请求的认证。你可以在这里找到一个可以正常工作的 例子 。
首先,为了能够登陆,你需要将用户名和密码发送 POST 请求到 "/ token"(或者你设置的获取 token 的 Web Api 断点)。你可以很容易地使用 jQuery 来做到这一点:
- $.post("/token", $.param({username: "the username", password: "the password"})).done(function(token){
- //save the token in local storage
- localStorage.setItem("token", token);
- //...
- }).fail(handleError);
如果一切顺利,则可以将获得 JWT token,然后你可以将其保存在某个位置,通常在 Web 应用程序中,我们将它保存到 local storage 中。在移动设备上则取决于你使用的平台,但它们都具有允许你保存 token 的功能(例如 Android 的 SharedPreferences)。
对于上一节中的身份验证中间件,接受 JWT token 并将其转换为可以在控制器操作中访问的 User,则该请求必须具有 Authorization header。header 的值应该是 "Bearer",然后是 JWT token,例如:
- Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1l...
尽管你可以 "手动" 将授权标头添加到每个请求,但通常有自动执行的方法。例如 jQuery 中有一个时间可以允许你在发送请求之前做一些操作,例如在这里检查是否存在 token,如果有就加到 Authentication 头里。
- $.ajaxSetup({
- beforeSend: function(xhr) {
- if (localStorage.getItem("token") !== null) {
- xhr.setRequestHeader('Authorization', 'Bearer ' + localStorage.getItem("token"));
- }
- }
- });
如果你使用其他框架,也有类似的机制,例如 Angular 有 HttpInterceptors 。
最后,你只需要从本地存储中删除 token 即可注销:
- localStorage.removeItem("token")
需要注意的一件事情是,如果客户端执行的操作需要用户进行身份验证,并且请求中没有(有效)授权标头,则服务器将返回带有 401 状态码的响应。该响应还将具有 WWW-Authenticate:Bearer header。如果你收到这样的响应,则你可以通知用户需要验证身份。
全文完
原文出自 Rui Figueiredo 的博客,原文链接 《Secure a Web Api in ASP.NET Core》
来源: http://www.cnblogs.com/rocketRobin/p/8058760.html