asp.net core集成JWT的步骤记录
【什么是jwt】
json web token(jwt)是目前最流行的跨域身份验证解决方案。
jwt的官网地址:
通俗地来讲,jwt是能代表用户身份的令牌,可以使用jwt令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。
jwt中包含了身份认证必须的参数以及用户自定义的参数,jwt可以使用秘密(使用hmac算法)或使用rsa或ecdsa的公钥/私钥对进行签名。
【什么时候应该使用json web令牌?】
授权:这是使用jwt的最常见方案。一旦用户登录,每个后续请求将包括jwt,允许用户访问该令牌允许的路由,服务和资源。single sign on是一种现在广泛使用jwt的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
信息交换:json web令牌是在各方之间安全传输信息的好方法。因为jwt可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
【jwt有什么优势?】
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
- 服务器向用户返回一个 session_id,写入用户的 cookie。
- 用户随后的每一次请求,都会通过 cookie,将 session_id 传回服务器。
- 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高。
相比之下,jwt的实现方式是将用户信息存储在客户端,服务端不进行保存。每次请求都把令牌带上以校验用户登录状态,这样服务就变成了无状态的,服务器集群也很好扩展。
【jwt令牌结构】
在紧凑的形式中,json web tokens由dot(.)分隔的三个部分组成,它们是:
- header 头
- payload 有效载荷
- signature 签名
因此,jwt通常如下所示:
xxxxx.yyyyy.zzzzz
1.header 头
标头通常由两部分组成:令牌的类型,即jwt,以及正在使用的签名算法,例如hmac sha256或rsa。
例如:
{ "alg": "hs256", "typ": "jwt" }
然后,这个json被编码为base64url,形成jwt的第一部分。
2.payload有效载荷
payload 部分也是一个 json 对象,用来存放实际需要传递的数据。jwt 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (not before):生效时间
- iat (issued at):签发时间
- jti (jwt id):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:
{ "sub": "1234567890", "name": "john doe", "admin": true }
注意,jwt 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 json 对象也要使用 base64url 算法转成字符串。
3.signature 签名
signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 header 里面指定的签名算法(默认是 hmac sha256),按照下面的公式产生签名。
hmacsha256( base64urlencode(header) + "." + base64urlencode(payload), secret)
签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证jwt的发件人是否是它所声称的人。
把他们三个全部放在一起
输出是三个由点分隔的base64-url字符串,可以在html和http环境中轻松传递,而与基于xml的标准(如saml)相比更加紧凑。
下面显示了一个jwt,它具有先前的头和有效负载编码,并使用机密签名。
如果您想使用jwt并将这些概念付诸实践,您可以使用jwt.io debugger来解码,验证和生成jwt。
【json web令牌如何工作?】
在身份验证中,当用户使用其凭据成功登录时,将返回json web令牌。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。
每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送jwt,通常在authorization标头中。标题的内容应如下所示:
authorization: bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查authorization标头中的有效jwt,如果存在,则允许用户访问受保护资源。如果jwt包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。
如果在标authorization头中发送令牌,则跨域资源共享(cors)将不会成为问题,因为它不使用cookie。
下图显示了如何获取jwt并用于访问api或资源:
应用程序向授权服务器请求授权校验用户身份,校验成功,返回token应用程序使用访问令牌访问受保护的资源【asp.net core 集成jwt】
前面我们介绍了jwt的原理,下面我们在asp.net core实际项目中集成jwt。
首先我们新建一个demo asp.net core 空web项目
添加数据访问模拟api,valuescontroller
其中api/value1是可以直接访问的,api/value2添加了权限校验特性标签 [authorize]
using system; using system.collections.generic; using system.linq; using system.threading.tasks; using microsoft.aspnetcore.authorization; using microsoft.aspnetcore.mvc; namespace demo.jwt.controllers { [apicontroller] public class valuescontroller : controllerbase { [httpget] [route("api/value1")] public actionresult<ienumerable<string>> get() { return new string[] { "value1", "value1" }; } [httpget] [route("api/value2")] [authorize] public actionresult<ienumerable<string>> get2() { return new string[] { "value2", "value2" }; } } }
添加模拟登陆,生成token的api,authcontroller
这里模拟一下登陆校验,只验证了用户密码不为空即通过校验,真实环境完善校验用户和密码的逻辑。
using system; using system.collections.generic; using system.identitymodel.tokens.jwt; using system.linq; using system.security.claims; using system.text; using system.threading.tasks; using microsoft.aspnetcore.authorization; using microsoft.aspnetcore.http; using microsoft.aspnetcore.mvc; using microsoft.identitymodel.tokens; namespace demo.jwt.controllers { [route("api/[controller]")] [apicontroller] public class authcontroller : controllerbase { [allowanonymous] [httpget] public iactionresult get(string username, string pwd) { if (!string.isnullorempty(username) && !string.isnullorempty(pwd)) { var claims = new[] { new claim(jwtregisteredclaimnames.nbf,$"{new datetimeoffset(datetime.now).tounixtimeseconds()}") , new claim (jwtregisteredclaimnames.exp,$"{new datetimeoffset(datetime.now.addminutes(30)).tounixtimeseconds()}"), new claim(claimtypes.name, username) }; var key = new symmetricsecuritykey(encoding.utf8.getbytes(const.securitykey)); var creds = new signingcredentials(key, securityalgorithms.hmacsha256); var token = new jwtsecuritytoken( issuer: const.domain, audience: const.domain, claims: claims, expires: datetime.now.addminutes(30), signingcredentials: creds); return ok(new { token = new jwtsecuritytokenhandler().writetoken(token) }); } else { return badrequest(new { message = "username or password is incorrect." }); } } } }
startup添加jwt验证的相关配置
using microsoft.aspnetcore.authentication.jwtbearer; using microsoft.aspnetcore.builder; using microsoft.aspnetcore.hosting; using microsoft.aspnetcore.mvc; using microsoft.extensions.configuration; using microsoft.extensions.dependencyinjection; using microsoft.identitymodel.tokens; using system; using system.text; namespace demo.jwt { public class startup { public startup(iconfiguration configuration) { configuration = configuration; } public iconfiguration configuration { get; } // this method gets called by the runtime. use this method to add services to the container. public void configureservices(iservicecollection services) { //添加jwt验证: services.addauthentication(jwtbearerdefaults.authenticationscheme) .addjwtbearer(options => { options.tokenvalidationparameters = new tokenvalidationparameters { validateissuer = true,//是否验证issuer validateaudience = true,//是否验证audience validatelifetime = true,//是否验证失效时间 clockskew = timespan.fromseconds(30), validateissuersigningkey = true,//是否验证securitykey validaudience = const.domain,//audience validissuer = const.domain,//issuer,这两项和前面签发jwt的设置一致 issuersigningkey = new symmetricsecuritykey(encoding.utf8.getbytes(const.securitykey))//拿到securitykey }; }); services.addmvc().setcompatibilityversion(compatibilityversion.version_2_2); } // this method gets called by the runtime. use this method to configure the http request pipeline. public void configure(iapplicationbuilder app, ihostingenvironment env) { ///添加jwt验证 app.useauthentication(); if (env.isdevelopment()) { app.usedeveloperexceptionpage(); } app.usemvc(routes => { routes.maproute( name: "default", template: "{controller=home}/{action=index}/{id?}"); }); } } }
最后把代码里面用到的一些相关常量也粘贴过来,const.cs
namespace demo.jwt { public class const { /// <summary> /// 这里为了演示,写死一个密钥。实际生产环境可以从配置文件读取,这个是用网上工具随便生成的一个密钥 /// </summary> public const string securitykey = "migfma0gcsqgsib3dqebaquaa4gnadcbiqkbgqdi2a2ej7m872v0afyosdjt2o1+sitiejswtlju8/wz2m7gstexajked+lka6dsty8gt9uwfgvqo6ukjvlg5ex7pigoodvqaeghbus7jziyu5rvi543nndapfnjsas96msa7l/md7rte2drj6hf3ozjjpmpzuqi/b1qjb5h3k3pnwidaqab"; public const string domain = "http://localhost:5000"; } }
到这里,已经是我们项目的所有代码了。
如果需要完整的项目代码,github地址:https://github.com/seventiny/demo.jwt
【jwt测试】
我们找一个趁手的工具,比如fiddler,然后把我们的web站点运行起来
首先调用无权限的接口:
正确地返回了数据,那么接下来我们测试jwt的流程
1. 无权限
首先我们什么都不加调用接口:http://localhost:5000/api/value2
返回了状态码401,也就是未经授权:访问由于凭据无效被拒绝。 说明jwt校验生效了,我们的接口收到了保护。
2.获取token
调用模拟登陆授权接口:http://localhost:5000/api/auth?username=zhangsan&pwd=123
这里的用户密码是随便写的,因为我们模拟登陆只是校验了下非空,因此写什么都能通过
成功得到了响应
然后我们得到了一个xxx.yyy.zzz 格式的 token 值。我们把token复制出来
3.在刚才401的接口请求header中添加jwt的参数,把我们的token加上去
再次调用我们的模拟数据接口,但是这次我们加了一个header:
把内容粘出来
user-agent: fiddler host: localhost:5000 authorization: bearer eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjuymyioiixntywmzq1mdixiiwizxhwijoxntywmzq2odixlcjodhrwoi8vc2nozw1hcy54bwxzb2fwlm9yzy93cy8ymda1lza1l2lkzw50axr5l2nsywltcy9uyw1lijoiemhhbmdzyw4ilcjpc3mioijodhrwoi8vbg9jywxob3n0ojuwmdailcjhdwqioijodhrwoi8vbg9jywxob3n0ojuwmdaifq.x7slk4ho1hzc8sr8_mcvtb6veylz_v-5eahvxtids-o
这里需要注意bearer 后面是有一个空格的,然后就是我们上一步获取到的token
嗯,没有401了,成功返回了数据
4.jwt的token过期
我们且倒一杯开水,坐等30分钟(我们代码中设置的过期时间),然后再次调用数据接口:http://localhost:5000/api/value2
又变成了401,我们看下详细的返回数据
这里有标注,错误描述 token过期,说明我们设置的token过期时间生效了
【结束】
到这里,我们jwt的简介以及asp.net core 集成jwt已经完美完成,当然了这只是一个demo,在实际的应用中需要补充和完善的地方还有很多。
如果想要完整项目源码的,可以参考地址:https://github.com/seventiny/demo.jwt
如果有幸能帮助到你,高抬贵手点个star吧~
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
推荐阅读
-
asp.net core集成JWT的步骤记录
-
(14)ASP.NET Core 中的日志记录
-
ASP.NET Core 2.0集成Office Online Server(OWAS)实现办公文档的在线预览与编辑(支持word\excel\ppt\pdf等格式)
-
ASP.NET Core+Docker+Jenkins实现持续集成的完整实例
-
asp.net core 使用 TestServer 来做集成测试的方法
-
ASP.NET Core 3.0 一个 jwt 的轻量角色/用户、单个API控制的授权认证库
-
.Net Core 集成 Kafka的步骤
-
ASP.NET Core 集成 React SPA应用的步骤
-
asp.net core 系列之webapi集成Dapper的简单操作教程
-
asp.net core 系列之webapi集成EFCore的简单操作教程