前言
JWT是目前最为流行的接口认证方案之一imtoken,有关JWT协议的详细内容,请参考:
今天分享一下在使用 JWT 在项目中遇到的一个问题,主要是一个协议的细节,非常容易被忽略,如果不是自己遇到,或者去看源码的实现,我估计至少80%的人都会栽在这里,下面来还原一下这个问题的过程,由于这个问题出现有一定的概率,不是每次都会出现,所以才容易掉坑里imtoken。
集成JWT
在Asp.Net Core中集成 JWT 认证的方式在网络上随便一搜就能找到一堆imtoken,主要有两个步骤:
在IOC容器中注入依赖
// 添加这一行添加jwt验证:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = newTokenValidationParameters
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds( 30),
ValidateIssuerSigningKey = true, //是否验证SecurityKey
ValidAudience = Const.Domain, //Audience
ValidIssuer = Const.Domain, //Issuerimtoken,这两项和前面签发jwt的设置一致
IssuerSigningKey = newSymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey)) //拿到SecurityKey
展开全文
应用认证中间件
// 添加这一行 使用认证中间件
app.UseAuthentication;
if(env.IsDevelopment)
app.UseDeveloperExceptionPage;
app.UseMvc(routes =>
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
在Controller
[ ApiController] // 添加这一行
publicclassMyBaseController: ControllerBase
提供一个认证的接口imtoken,用于前端获取token
[ ]
publicIActionResult Get( stringuserName, stringpwd )
if(! string.IsNullOrEmpty(userName) && ! string.IsNullOrEmpty(pwd))
varclaims = new[]
newClaim(JwtRegisteredClaimNames.Nbf, $" {new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds}" ) ,
newClaim (JwtRegisteredClaimNames.Exp, $" {new DateTimeOffset(DateTime.Now.AddMinutes( 30)).ToUnixTimeSeconds} " ),
newClaim(ClaimTypes.Name, userName)
varkey = newSymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
varcreds = newSigningCredentials(key, SecurityAlgorithms.HmacSha256);
vartoken = newJwtSecurityToken(
issuer: Const.Domain,
audience: Const.Domain,
claims: claims,
expires: DateTime.Now.AddMinutes( 30),
signingCredentials: creds);
returnOk( new
token = newJwtSecurityTokenHandler.WriteToken(token)
else
returnBadRequest( new{ message = "username or password is incorrect."});
至此,你的应用已经完成了集成 JWT 认证imtoken。
本文为 Gui.H 原创文章,更多高质量博文,欢迎关注公众号 dotnet之美 imtoken。
本文为 Gui.H 原创文章,更多高质量博文,欢迎关注公众号 dotnet之美 imtoken。
本文为 Gui.H 原创文章,更多高质量博文,欢迎关注公众号 dotnet之美 imtoken。
直接上代码,下面这段代码是我用来能复现该大坑的示例,有空的可以按照该代码重现下面的问题imtoken。
usingMicrosoft.IdentityModel.Tokens;
usingSystem.IdentityModel.Tokens.Jwt;
usingSystem.Security.Claims;
usingSystem.Text;
varSecurityKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB";
varDomain = "";
varemail = "username@qq.com";
varuserName = "阿哈";
varclaims = new[]
newClaim(JwtRegisteredClaimNames.Nbf, $" {new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds}" ) ,
newClaim (JwtRegisteredClaimNames.Exp, $" {new DateTimeOffset(DateTime.Now.AddMinutes( 30)).ToUnixTimeSeconds} " ),
newClaim( "Name", userName),
newClaim( "Email", email),
varkey = newSymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey));
varcreds = newSigningCredentials(key, SecurityAlgorithms.HmacSha256);
vartoken = newJwtSecurityToken(
issuer: Domain,
audience: Domain,
claims: claims,
expires: DateTime.Now.AddMinutes( 30),
signingCredentials: creds);
varJWTToken = newJwtSecurityTokenHandler.WriteToken(token);
Console.WriteLine(JWTToken);
Console.ReadLine;
上面代码运行的结果是:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.RBtP7zroK7YueGlDdZNHGy3tT8-xcGkf8ZyiTL81w2I
我们知道Token由三部分组成,使用 . 分割,如果是标准的Jwt协议加密的,那这三部分均为 Base64加密 (此处不准确,下文解释为什么),也可以说就是明文,我们将三部分内容进行Base64解密看看imtoken。
我们在线验证一下我们的Jwt是否符合标准:打开网站: 填进去:
然后将代码中用的 SecurityKey 填到图中标记的位置
显示签名认证通过imtoken。
头
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9
{ "alg": "HS256", "typ": "JWT", "cty": "JWT"}
载荷
eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ
"nbf": "1653400694",
"exp": 1653402494,
"Name": "阿哈",
"Email": "username@qq.com",
"iss": "",
"aud": ""
签名
RBtP7zroK7YueGlDdZNHGy3tT8-xcGkf8ZyiTL81w2I
到目前为止一切都十分顺利imtoken。
既然Token的内容前端直接可以通过base64解密出来,那在需要展示用户名的地方,我们就可以直接解析token的载荷,然后获得 Name ,下面是使用在线base64工具解密上面的token载荷内容,可以看到用户名为 啊哈 imtoken。
逻辑没有任何问题,那就开始前端进行解析token中的用户名用于展示在个人中心吧imtoken。下面是在 Vue3 框架和 Piana 中的演示, window.atob 是浏览器自带base64decode的方法
exportconst useUserStore = defineStore({
id: 'user',
state: => {
return{
token: '',
getters: {
accessToken: (state) => {
returnstate.accesstoken || localStorage.getItem( "accesstoken");
* 获取token中解密后的用户信息
userInfo(state) {
var token = state.token || localStorage.getItem( "accesstoken");
if(!token || token == '') {
returnnull;
var json = window.atob(token.split( ".")[1]);
returnJSON.parse(json);
在需要获取用户名的地方使用
computed:{
...mapState(useUserStore, [ "userInfo"]),
感觉一切都很优雅的写完了代码,但是实际运行会报错: 这里为了方便是直接在浏览器的调式器中执行的报错的意思来看是说我们的字符串没用正确的加密(就是它说咱这个字符串不是合法的base64加密)imtoken。可是我们通过一些在线base64解密工具,还有Jwt的debugger工具都能解密出来明文。而且这不是我第一次将token拿出来进行解密了,之前也都没问题。
是不是token有问题?经过测试,调用接口完全不会有问题,只是前端解密时报错,排除token不合法imtoken。
前端的atob函数存在bugimtoken?那我们在后端用c#的base64解密一下看看:
居然后端解密也报错了,头部解密成功,载荷部分解密异常,和前端报错一样都是说字符串不是合法的base64内容,不知道你是不是偶尔遇到过这个问题,如果没有,那你更要往下看了,不然以后遇到了,要耽误不少时间去排查了imtoken。
上面遇到的问题曾经花了我不少时间去排查,关键是有工具能解密的还有工具不能解密,一时不知道到底是谁的问题了,抱着试试看的态度,看看源码生成token三部分的字符串过程imtoken。
既然token是这个函数生成的,那就直接看它的实现,直接F12即可,这个包是不是框架自带的,所以能直接通过vs看源码,比较方便的imtoken。
源码如下imtoken, encodedPayload 根据它的命名不难看出是机密后的载荷,我们需要看的是它如何加密的
查看 jwtToken.EncodedPayload 这个属性怎么来的(F12)
图中标记imtoken了三个数字:
上一步imtoken我们逆向找到加密后的属性 EncodedPayload
EncodedPayload 属性里面用到了另一个属性 Payload ,imtoken我们需要找 Payload 哪里赋值的
Payload 是在构造函数中根据传参内容进行初始化的imtoken。
上一步imtoken我们逆向找到加密后的属性 EncodedPayload
EncodedPayload 属性里面用到了另一个属性 Payload ,imtoken我们需要找 Payload 哪里赋值的
Payload 是在构造函数中根据传参内容进行初始化的imtoken。
上一步我们已经锁定进加密的逻辑在 Payload.Base64UrlEncode 中imtoken,看 JwtPayload 的类定义
可以看出imtoken,载荷的加密和我们想象的一样简单,把 JwtPayload 对象转成 Json ,然后进行 Base64Url 加密 5. 现在只剩 Base64UrlEncoder.Encode 的实现能为我们揭秘了
整体看下类定义,我们调用的 Encode 按标记顺序,依次调用了三个重载方法,最终实现都标记为3的那个方法imtoken。6. 不知道你有没有注意到这些内容
看到这里我恍然大悟了一点imtoken,再看看他这里面的decode方法
看见了吧imtoken,我们因为是单纯的Base64加解密,其实不然,在进行 Convert.FromBase64String(decodedString) 解密前还需要进行一些字符串的替换,我赶紧看下上面出问题的载荷内容,发现其中有 _ 这个字符,我赶紧将其进行替换成 + ,再次尝试:
eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi_5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ
// 替换后
eyJuYmYiOiIxNjUzNDAwNjk0IiwiZXhwIjoxNjUzNDAyNDk0LCJOYW1lIjoi6Zi+5ZOIIiwiRW1haWwiOiJ1c2VybmFtZUBxcS5jb20iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ
果然如此,替换后解密成功了,只有一个汉字的编码问题imtoken。
这下找到问题了imtoken,优化下前端的解密代码
userInfo(state) {
vartoken = state.token || localStorage.getItem( "accesstoken");
if(!token || token == '') {
returnnull;
token = token.replace( "_", "/").replace( "-", "+") // 添加这一行
varjson = window.atob(token.split( ".")[ 1]);
returnJSON.parse(json);
问题解决了imtoken。
注意官方对加密过程的描述
哈哈imtoken,是不是草率了,并不是 Base64 加密~~
总结
我们都以为Jwt三部分是用 Base64 加密,其实不完全对,因为他确切的加密方式是 Base64Url 加密,没有深入理解的我们只以为就是纯粹的base64,而且在大部分情况下确实是这样,更加坚定了我们这种错误认知imtoken。而只有当Base64加密后出现字符 + 或 / 时,才会有所不同,希望对大家有帮助。
CNBS 机构相信,在不久的将来,加密货币市场将面临不必要、过时和“垃圾”的加密货币“大清洗”加密货币。业内人士告诉 CNBS,市场上越来越多的主要参与者开始表示,大多数跟比特币一样的加密货币,但没有被高度欢迎的加密货币迟早会崩溃并消失。当然,这样的说法不适用于前500名和前1000名的加密货币。...
据美股行情显示,纳指期货现跌2%,标普500指数期货跌1.3%,道指期货跌近0.9%btc。联动币圈,就变成BTC跌3%,ETH跌2%,今日大部分币种都有2%左右的跌幅。 从市场情绪变化上看,较昨日还高了2个点位,来到了12,无需兴奋,是由于情绪尚未传导到位,今日15点,等欧洲投资者起床了,欧洲的加...
最近市场因为stETH脱锚事件为导火索eth,导致以太坊的加速下跌!今天小新就深度解析一下整个事件的前因后果! 微博 区块链小新kol 公众号 贝尔投研小新 早在2020 年 10 月Cobie 曾发表过一篇博文介绍eth了 Lido: 简而言之,Lido 是一个自治的,且将用户所质押的以太坊进行...
数字资产投资管理公司DAIM进行的研究显示,按市值计算的加密货币的四个最大稳定币此后在两年内大幅增长usdt。根据 DAIM 的数据,从2020年4月至今,USDT、USDC、BUSD和DAI上涨了20多倍,从70亿美元增至1470亿美元。DAIM在其报告中表示,这意味着生态系统有额外的 1400亿...
如何练就一双“慧眼”押中蓝筹NFT项目?如果你押中了蓝筹的NFTnft,基本上等于是赚大发了,什么是蓝筹NFT? NFT的成交价非常惊人,都快上天了,最贵的NFT加密朋克Cryptopunkss成交价就8000枚ETH,换算人民币高达1.5个,NFT这么火爆,大家都想分一杯羹,但不是所有人都能挣钱的...
IntoTheBlock 研究负责人 Lucas Outumuro 在周五的一条推文中表示,在以太坊主网与信标链合并后,以太坊 (ETH) 将成为一种通货紧缩的加密货币eth。到目前为止,以太坊 (ETH) 是通货膨胀的,但在合并之后,由于 EIP-1559 提议燃烧 ETH 而不是将其提供给矿工,...