V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nobject
V2EX  ›  Web Dev

oidc 在单点登录场景下是怎么使用的? id_token,access_token 还是不太明白

  •  1
     
  •   nobject · 2023-10-16 13:12:58 +08:00 · 2156 次点击
    这是一个创建于 397 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在看了一点oidc相关的协议内容,想以此来做单点登录。但看了协议,还有好多不太明白的地方。

    oidc相比oauth2来说, 多了一个id_token,用于身份认证,而本身oauth2部分的又有access_tokenrefresh_tokenaccess_token是用于保护资源的,refresh_token是用于刷新access_token的.

    那举个简单的使用场景:
    一个平台,有以下几个服务, 统一身份认证的服务identify(基于oidc协议实现的idp) 服务 A(a.com) 服务 B(b.com) 那在 oidc 体系中,a.com, b.com 实际上是两个应用(client)

    正常的流程就是:
    a.com判断本地是否有token(基于localstorage是否有相关的存储), 如果有,则每次正常请求携带token, 如果没有,则携带 client 信息(client_id,redirct_uri,scope 等)跳转至identify服务的登录页面, identify服务会对用户的用户名密码认证,认证成功后跳转至a.com/callback?code=xxxxx&state=xxx a.com 根据code码获取到id_token,access_tokenrefresh_token

    那么有以下疑问:
    问题 1: 前端中header头上使用的authorization是塞id_token还是access_token? 按照定义,应该是id_token吧。 那access_token的作用是不是如果我的资源只是限定有没有登录,登录就可以访问所有的资源(api 的鉴权有另外的鉴权服务),那其实access_token在这种场景下就没有意义?

    问题 2:refresh_token的作用是用来刷新id_token还是access_token? 因为refresh_token, access_token是 oauth2 就有的概念,最开始 refresh_token 肯定是刷 access_token 的, 那如果使用id_token,因为 jwt 的签发后无法回收,id_token理论上也是时效设置成比较短的一个时长,那这样必然是需要 refresh_token 去保证自动时长续期,所以可以使用返回的 refresh_token 值去 刷新 id_token 么?

    问题 3:在多应用场景的单点登录下,比如a.com按照以上流程登录了,那理论上在同一个浏览器下,访问b.com应该也是登录状态的。那这个登录状态是怎么判断呢?b.com的本地存储里肯定是没有存储 id_token 的。 那此时,是不是在a.com登录的时候,登录成功其实就应该在identify的前端的localstorage里存储id_token?那这样问题又来了,刷新token了怎么办? 刷新 token 一般在应用端发起, 但又影响不到identify服务的前端存储. 没有想到比较完美的闭环

    19 条回复    2024-01-10 01:30:28 +08:00
    tool2d
        1
    tool2d  
       2023-10-16 14:25:45 +08:00
    nobject
        2
    nobject  
    OP
       2023-10-16 15:14:14 +08:00
    @tool2d 谢谢,看这图明白了点,id token 是用于认证,access token 是用于 api 访问权限判断。那我请求一个 api ,如果后端去解析 id token 的 jwt ,那前端不是得把 id token 与 access token 一并传给后端?
    tool2d
        3
    tool2d  
       2023-10-16 15:28:27 +08:00
    @nobject 大部分情况只传一个 access token 吧,都是授权给第三方 app 访问的,很容易就过期了,需要刷新。

    以前 access token 不规范,现在很多公司用 JWT 格式,个人感觉和 id token 很接近了。
    nobject
        4
    nobject  
    OP
       2023-10-16 15:52:58 +08:00
    @tool2d 开放给第三方应用的确实得传 access_token ,但我现在的应用场景是单点登录,我们一个大平台,里面可能有各个子系统,这些系统其实都不算是第三方的应用,理论上是属于我们公司自己的产品,oidc 这边其实就是要做一个统一认证平台,在 api 的授权访问这块,其实有 rbac 的鉴权模块统一处理的。从需求方面,我觉得我们可能用的是 id_token, 不是 access_token
    FuryBean
        5
    FuryBean  
       2023-10-16 15:59:11 +08:00
    问题 2:都刷新,一块返回。和第一次登录区别不大,只是 grant_type 参数换成了 refresh_token 。
    问题 3:不需要考虑的这么复杂,可以假设 a.comb.com 的登录态依然是通过 cookie + session 来维护。a.com 登录之后,b.com 去访问 oidc 登录页面的时候,不会要求你输入用户名和密码,会直接跳转回 b.com 的 oidc 的 callback 。因为这个过程非常快,连续进行了几个 302 ,你会觉得好像登录 a.com 的时候,b.com 已经登录了一样,实际上依然是你访问 b.com 的时候才进行的登录。
    FuryBean
        6
    FuryBean  
       2023-10-16 16:04:23 +08:00
    再回复下「那理论上在同一个浏览器下,访问 b.com 应该也是登录状态的。那这个登录状态是怎么判断呢?」。

    答案非常简单,假设你的 oidc 的 provider 是在 oidc.com ,用户的登录态实际上是用的 oidc.com 的 cookie 来维持的。

    a.comb.com 进行 oidc 登录,会重新走一遍 oidc 的流程。a.comb.com 的登录态各自维护,可以继续用 cookie + session ,也可以用 authorization 这种 header 的方式。
    8rmEHZ8WhVHVOb0E
        7
    8rmEHZ8WhVHVOb0E  
       2023-10-16 17:25:15 +08:00 via Android
    access_token 是给你的 a 应用和 b 应用的后端用的,而不是前端,你的 A 应用前端和 A 应用后端交互你需要自己实现。

    a 站点登录后 b 站点也自动登录,这个在 cas 和 OIDC 中并没有统一实现,都是你访问要登录的页面时,才跳转到 sso 登录。如果你某个页面,没有登录也可以访问,但是如果 sso 登录了,需要同步登录,常见做法,假设域名为 sso.domain.com
    a.domain.com
    c.domain.com
    sso 登录时,在根域名 domain.com 下写两个 cookie ,一个写明文明文 UID ,一个写 UID 加密字符,应用 a 和 b 后端随时检测这两个 cookie 是否存在,如果存在,且检测加密是否为你的系统生成的 cookie 而不是用户自己乱写入的,则跳转到 sso 认证再跳回当前浏览页面,即可实现非必须登录页面的自动同步登录状态,同理,可以用该 cookie 实现同步退出,a 应用退出后跳转至 sso 退出,sso 删除这两个 cookie ,访问已经登录的 b.domain.com ,该站点检测到根域名下的 cookie 已经失效,清楚当前站点登录状态。

    如果应用和 sso 不在同一个域名下,例如:
    sso.qq.com
    www.qq.com
    www.paipai.com
    此时,paipai 站点无法读取到 qq.com 下的 cookie ,并不知道用户已经在 sso.qq.com 下登录了,需要手动跳转至 sso.qq.com 认证。常见方案是,sso.qq.com 登录后 iframe 或者 jsonp 方式调用 sso.paipai.comsso.paipai.com 会同步在 paipai.com 根域名下写 cookie ,www.paipai.com 检测到这两个 cookie 存在且合法的情况下跳转至 sso.qq.com 认证。
    8rmEHZ8WhVHVOb0E
        8
    8rmEHZ8WhVHVOb0E  
       2023-10-16 17:34:16 +08:00 via Android
    @FuryBean OIDC 和 Cas 其实都适合内部 sso ,因为 git 、oa 等很多内部应用是第三方厂商开发的,所以需要一个开放统一的认证协议。如果你的 sso 是 to c 的话,不如自定义 sso 协议来的方便。你可以观察 qq 淘宝这些大厂商,他们做起来的时候,还没有这些玩意,所以他们的 to c 的账户系统都是自定义协议的,而小米、vivo 、oppo 的 sso ,你可以去登录页面观察参数,依旧没有使用 cas 和 oidc ,而是选择了自定义的方式,不过原理都是差不多的。

    但是这些厂商的内部员工用的 sso 都支持 cas 这些,应该就是为了兼容采购的第三方企业应用。

    2C 的话,自定义协议更方便也更符合自己公司的需求
    nobject
        9
    nobject  
    OP
       2023-10-17 13:13:46 +08:00
    @FuryBean 嗯,这么说流程还是蛮清晰的。但其实我纠结的点在哪呢,就是明明使用 oidc 了,那边也返回 id_token 了,但是这个 id_token 的正确使用指南我 get 不到,你像你说的这个流程,感觉更像是传统的 sso 解决方案,用不用 oidc 其实没啥差别,我请求 oidc.com ,只要告诉应用认证成功和用户信息就 ok 了,应用的 session 自己维护,也不需要和 id_token 有任何关联
    nobject
        10
    nobject  
    OP
       2023-10-17 13:20:51 +08:00
    @xiaomada 我们的目标是所有的都会接入到这个账号体系中,包括 gitlab 之后也会接入,但首先是要满足我们整个云平台下的各自子系统的登录,所以最后选择了比较统一的 oidc 协议。
    nobject
        11
    nobject  
    OP
       2023-10-17 13:41:00 +08:00
    @xiaomada 谢谢,不同域名下的实现方式对我很有帮助。对于单点登录与登出,oidc 的 core 协议里确实没有,但有几个补充协议,好像通过前端通道实现的方式和你下面说的不在同一个域名下的实现方式很类似,就是需要 iframe 去做辅助同步通知到其他域。

    基于 oidc 协议的,最后返回的 id_token ,我在想,如果使用传统的 cookie + session 的方式,那他协议返回的 id_token 到底怎么使用呢?看了一些开源的基于 oidc 协议作身份认证的产品,在应用的实现范例大多数是怎么通过授权码去获取到 id_token 这些,就点到为止了
    8rmEHZ8WhVHVOb0E
        12
    8rmEHZ8WhVHVOb0E  
       2023-10-17 14:11:18 +08:00
    @nobject access_token 用于授权,因为 oidc 是基于 oauth2 的,而 oauth2 协议原本就有 access_token ,这个用于你更多的功能授权,比如你的 sso 里有一个余额,你在 scope 里授权了余额相关的功能,那么你的 access_token 就可以操作你 sso 提供的余额相关接口,access_token 的格式既可以是 jwt 格式,也可以是自己生成的随机字符串。

    id_token 固定为 jwt 格式,里面可以解析出基础用户信息。

    针对你问题里的 id_token 怎么使用这个问题,基本与 cas 协议类似, 流程大概如下:
    假设现在有商城 shop.qq.com ,后台需要接入员工 sso

    1 、员工访问 shop.qq.com/admin ,未登录重定向至 shop.qq.com/admin/login
    2 、shop.qq.com/admin/login 判断未登录,重定向至 sso.qq.com/oidc/login?一堆参数
    3 、sso 登录后重定向回 shop.qq.com/admin/login?code=xxxxxxxx
    4 、shop.qq.com/admin/login 页面通过 code 交换到 access_token 和 id_token
    5 、商城系统解析 id_token (或者使用 access_token 去 sso 的用户信息 api 交换),获得用户信息
    6 、商城系统从用户信息读取唯一标识,比如 user_id, 判断本系统 user 表是否存在(如果不存在写入 user 表),,然后写入登录信息

    这个流程其实 cas 协议没有太大的区别,只是参数和接口发生的了变化。
    和 oauth2 的区别就是用户端省略了点击确定授权,然后读取用户信息可以省略掉从 op 读取,而可以直接从 id_token 解析,除此之外基本就和你接入 QQ 登录一样没有区别
    FuryBean
        13
    FuryBean  
       2023-10-17 14:16:31 +08:00
    @nobject #9 OIDC 只是一套协议,目前还有一套流行的协议叫 SAML2.0 。支持这两种协议的 SSO 服务,第三方接入会非常方便,因为有很多开源的 SDK 。
    nobject
        14
    nobject  
    OP
       2023-10-17 15:22:11 +08:00
    @xiaomada id_token 按你的说法,只用于解析出来 userId ,如果子系统 user 表不存在,则写入到 user 表,然后子系统自己想怎么处理系统会话,它自行处理,其实这时候已经与 id_token 就无关了? 还有 id_token 与 access_token 的区别在于本身包含了用户的一些信息,但 access_token 如果需要获取用户信息的话,还需要通过 userinfo endpoint 去获取一次,如果本身不介意再去获取一次的话,其实可能就不需要 id_token 了?我这样理解对么?
    8rmEHZ8WhVHVOb0E
        15
    8rmEHZ8WhVHVOb0E  
       2023-10-17 16:00:09 +08:00
    @nobject 第一个问题,userId 是唯一标识,你可能还需要用户名、email 之类的,要啥你 jwt 里带啥就行了。第二个问题,没错,因为 oidc 本来就是在 oauth 里多返回了个 id_token 而已,其他没啥区别。
    8rmEHZ8WhVHVOb0E
        16
    8rmEHZ8WhVHVOb0E  
       2023-10-17 16:02:57 +08:00
    @nobject 你可以安装一个 maxkey 调试一下,或者 IDaas 厂商,authing 、阿里云 IDaas 、华为 OneAccess ,都可以免费试用,你接入一个应用调试一遍就好理解了,使用一下他们现成的产品也可以帮助你更好的构思自己的项目。华为的 OneAccess 比较简单好理解
    8rmEHZ8WhVHVOb0E
        17
    8rmEHZ8WhVHVOb0E  
       2023-10-17 16:06:57 +08:00
    @nobject 除了 id_token 读取信息之外,他们一般还会有一个同步器和连接器,不同系统叫法不一样,同步器就是从你现有的用户系统读取用户,连接器就是你的用户信息发生了变动,比如密码、email ,会同步到应用系统,一般是 sso 后端通知应用后端,否则你的 sso 里修改了 email ,其他应用要再次授权获取 token 时才能知道 email 修改了,用了连接器的话就可以直接同步过去了。
    nobject
        18
    nobject  
    OP
       2023-10-17 17:09:42 +08:00
    @xiaomada 嗯,好的,非常感谢,我去体验一下你上面说的一些 idaas 产品,加深一下相关概念的理解。
    Casbin
        19
    Casbin  
       311 天前
    OIDC 和 SSO 本身没有关系,OIDC 是一个认证协议,SSO 是指实现“单点登录”这个效果,也就是一个地方登录了,其他地方也自动登录,实现方式可以是不同的。这两者互相没关系,但是可以在同一个 IAM 系统中实现,具体可以看下 Casdoor 的实现:

    Casdoor OIDC: https://casdoor.org/docs/how-to-connect/oidc-client/
    Casdoor SSO: https://casdoor.org/docs/how-to-connect/single-sign-on/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2897 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 13:49 · PVG 21:49 · LAX 05:49 · JFK 08:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.