oidc-client.js踩坑吐槽贴

前言

前面选用了IdentityServer4做为认证授权的基础框架,感兴趣的可以看上篇<微服务下认证授权框架的探讨>,已经初步完成了authorization-code与implicit的简易demo(html+js 在IIS部署的站点),并实现了SSO,本想着将Demo迁移到vue工程是轻而易举,毕竟也没啥东西,最终拿到access_token,存储到store里,跟传统的jwt基本上一样,不出意外,意外总是会发生,不然也没办法水一篇帖子,玩笑归玩笑,如果有理解错误,或者使用不当的地方,欢迎拍砖,以免误人子弟
image

环境角色

  • vue2.0 cli
  • .net core 6.0

这里为什么不用vue3,尝试过,最终放弃了,本来就是半吊子的前端水平,vue3里的vite跟ts,极大的增加了学习成本,包升级后一些莫名其妙的bug,特别是vite,一些bug提示对我来说特别陌生与不友好(项目不报错,js控制台报错,好像是兼容之类的问题),总之还是太菜,解决不了,又菜又爱玩
image

准备动手

在IdentityServer4里,对于前端工程提供了oidc-client.js这个包,在github开源,不过在2021年左右就停止维护了,这里我收集了两条跟本次吐槽相关的Issues
https://github.com/IdentityModel/oidc-client-js/issues/1393
https://github.com/IdentityModel/oidc-client-js/issues/1360

OAuth2.0 与 OAuth2.1

吓了一跳,OAuth2.1是个什么玩意?来看看GPT的回答
image
原来官方还没有这个东西,市面上一些对OAuth2.0拓展或者增强的东西,搞了个OAuth2.1,或许只是一个草案,本次吐槽的坑点就是oidc-client.js里PKCE的使用,PKCE全称是 Proof Key for Code Exchange, 在2015年发布, 它是 OAuth 2.0 核心的一个扩展协议, 所以可以和现有的授权模式结合使用,比如 Authorization Code + PKCE,东西是好东西,但是强制使用,不与之前的OAuth2.0兼容,就要被骂了
image

啥是PKCE?

大白话,就是防止授权码(code)被坏人拦截,而设计的一个拓展协议,坏人拿到了code也换取不了access_token,它的实现过程也非常简单,三两句就能讲明白,就不画图了,直接上代码截图
三个角色,授权服务,前端工程,后端工程

  1. 认证中心登录,回调页面上多了两个参数

code_challenge_method 签名方法
code_challenge 签名

  1. 使用code换取access_token的时候,要多带一个code_verifier(随机生成的一个字符串) 参数,否则兑换失败

code_challenge = code_challenge_method(code_verifier)

  1. 协议约定,如果在认证中心生成code的时候,携带了code_challenge_method与code_challenge,那么在使用code兑换
    access_token的时候,必须携带code_verifier

如果这还听不明白,那我举个古代银票防伪的例子
张三去钱庄存银子的时候,跟钱庄做了约定,留下一句暗号(DNF)跟暗号的生成方式(取每个字的首字符)给钱庄,兑换者在用银票换银子的时候,钱庄会检查这张银票有没有跟张三做约定,如果有约定,则按照约定让兑换者提供明文,钱庄用兑换者提供的明文跟张三提供的暗号生成方式,来生成暗号,如果一致则允许兑换银子
银票 = code
暗号 = 签名(code_challenge )
暗号的生成方式 = 签名方法(code_challenge_method )
明文 = code_verifier(随机字符串)

为啥要喷oidc-client.js里PKCE的实现?

  1. PKCE关不了
var config = {
    authority: "https://localhost:6201",
    ...
    pkce:false
};

前面Issues有提过,提供了这个配置,但是不生效,总之一定会带上

  1. 回调逻辑存在问题
var userManager = new Oidc.UserManager(config);
userManager.signinRedirectCallback().then(function(user) {
    // 登录成功,可以在这里处理登录后的逻辑,例如重定向到首页或显示欢迎信息
    console.log('用户已登录:', user);
    window.location.href = '/home'; // 重定向到应用的主页
}).catch(function(error) {
    // 处理登录失败的情况
    console.error('登录失败:', error);
    alert('登录过程中发生错误,请重试。');
});

image
image
这是oidc-client.js里回调页面的处理,它自动做了处理,但是它绕过后端程序,直接向认证服务发起,这里返回400错误,是因为该客户端必须校验秘钥,我们一般将秘钥存储在后端程序,前端程序请求后端,后端带上客户端秘钥,将请求转发给认证服务,这才是authorization-code标准的认证流程,也相对安全

  1. 相同的版本,在(html+js)站点下,并没有启用PKCE,而在vue-cli工程,默认启用PKCE

image
image
这点也是我最郁闷的地方,最开始我怀疑是版本问题导致的,后面我降级到相同的版本,但是结果却还是一样,折腾了好久,依旧没有解决,*******
image

解决办法

  1. 后端强行关闭PKCE认证

既然vue-cli工程里发起的获取code,会携带PKCE认证信息,那我在重定向地址里,把这两个参数移除,那就搞定收工了

  1. 啃oidc-client.js源码,翻Issues,看看有没有转机

先看Issues,把问题关键字贴进去搜一下,很遗憾没有找到有用的东西,源码简单的翻了下,也没啥帮助

  1. 逆向oidc-client.js,重写回调页面逻辑

既然我们缺code_verifier,那就去找,打印config.userStore,就找到了,这得感谢作者没有每次都删除该对象,不然可能就真的芭比Q了,知道了位置,我们直接从Localstorage取,然后根据时间戳取最新的那条记录,然后提取出code_verifier

function getCodeVerifier() {
  const allKeys = [];
  const items =[];
  for (let i = 0; i < localStorage.length; i++) {
    allKeys.push(localStorage.key(i));
  }
  const pattern = /^oidc\./;
  const oidcKeys = allKeys.filter(key => pattern.test(key));
  for(let i =0;i< oidcKeys.length;i++){
     const str = localStorage.getItem(oidcKeys[i]);
     if(str != null && str != ''){
      const model = JSON.parse(str);
      if(model.created == '' || model.created == null || model.created == undefined){
        continue
      }
      items.push(model)
      localStorage.removeItem(oidcKeys[i])
     }
  }
  const item = items.reduce((nearest, current) => {
    return (nearest.created > current.created) ? nearest : current;
  });
  return item.code_verifier;
}

最终效果

image

posted @ 2024-05-31 19:29  提伯斯  阅读(435)  评论(3编辑  收藏  举报