1K Star 46K Fork 3.6K

GVPdromara / Sa-Token

 / 详情

SSO模式三,多用户登录同一账号后退出异常(单点注销)

已完成
创建于  
2022-03-18 10:10

使用版本:

1.29.0

复现步骤:

您好,我现在是基于咱们 SSO 模块模式三搭建了一个 SSO Server 应用(后面简称 Server 应用)和一个 SSO Client 应用(后面简称 Client 应用),暂时是这一个,未来肯定还有其他 Client 应用。

下方出现的名词解释:

  • PC-1 某台 PC 设备
  • PC-2 某台 PC 设备

发生问题的操作步骤如下:

  1. 首先我在 Server 应用的用户数据库中初始化了一个账号:test123。
  2. 用户小明在 PC-1 中打开浏览器访问了 Client 应用,通过 SSO 登录了账号 test123。
  3. 随后,用户小红在 PC-2 中打开浏览器访问了 Client 应用,通过 SSO 也登录了账号 test123。
  4. 用户小明在使用完 Client 应用后,希望退出这次登录,于是在 Client 应用中点击了退出按钮。
  5. 但是用户小明却发现自己的账号并没有退出成功,而此时用户小红正在访问系统,突然发现自己账号莫名其妙的退出了。
  6. 并且用户小明再去点退出按钮,发现怎么也无法退出。

希望结果:

用户小明退出 test123 账号成功,同时不影响用户小红继续使用 test123 账号。

评论 (8)

Charles7c 创建了任务

问题排查:

出现这个问题后,我进行了一定的排查。

基于我现有的配置:

  1. 用户小明登录成功后,在 Server 应用 Redis 中会出现如下两个键值:
  • satoken:login:session:1,存储着 SLO_CALLBACK_SET_KEY_ (单点注销回调地址列表)和 tokenSignList (token列表)等信息
  • satoken:login:token:ab6d8b32-908c-4245-8fc8-0fd96ff51b42,存储着 loginId,1
  1. 用户小明登录成功后,在 Client 应用 Redis 中会出现如下两个键值:
  • satoken:login:session:1,存储着 tokenSignList (token列表)等信息
  • satoken:login:token:eeb63302-4eaa-4a4a-bc9a-5b1d95e33de5,存储着 loginId,1
  1. 用户小红登录成功后,在 Server 应用 Redis 中,satoken:login:session:1 存储的 tokenSignList (token列表)会增加一个新的 token 信息,同时会增加一个新的 satoken:login:token:xxxxx 键值,同样存储着 loginId,1
  2. 用户小红登录成功后,在 Client 应用 Redis 中变化同上
  3. 当用户小明点击退出后,在 Server 应用 Redis 中,satoken 相关键值对全部清空
  4. 当用户小明点击退出后,在 Client 应用 Redis 中,属于后来登录用户小红的 token 被清除掉了

这个观察的结果,也在预料之内,Server 应用注销部分关键源码如下:

/**
 * SSO-Server端:单点注销 [模式三] 
 * @return 处理结果 
 */
public static Object ssoServerLogout() {
    // 获取对象 
    SaRequest req = SaHolder.getRequest();
    SaSsoConfig cfg = SaManager.getConfig().getSso();
    StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;

    // 获取参数 
    String loginId = req.getParam(ParamName.loginId);
    String secretkey = req.getParam(ParamName.secretkey);

    // 遍历通知Client端注销会话 
    // step.1 校验秘钥 
    SaSsoUtil.checkSecretkey(secretkey);

    // step.2 遍历通知Client端注销会话 
    SaSsoUtil.forEachSloUrl(loginId, url -> cfg.sendHttp.apply(url));

    // step.3 Server端注销 
    stpLogic.logout(loginId);

    // 完成
    return SaSsoConsts.OK;
}

最后是根据 loginId 注销的,所以 Server 应用 Redis 内 satoken 相关键值对全部被清空了。

而 Client 应用注销部分关键源码如下:

/**
 * SSO-Client端:单点注销的回调  [模式三] 
 * @return 处理结果 
 */
public static Object ssoLogoutCall() {
    // 获取对象 
    SaRequest req = SaHolder.getRequest();
    StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;

    // 获取参数 
    String loginId = req.getParam(ParamName.loginId);
    String secretkey = req.getParam(ParamName.secretkey);

    SaSsoUtil.checkSecretkey(secretkey);
    stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
    return SaSsoConsts.OK;
}
/** 
  获取指定账号id的tokenValue 
 * <p> 在配置为允许并发登录时,此方法只会返回队列的最后一个token,
 * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId 
 * @param loginId 账号id
 * @return token值 
 */
public String getTokenValueByLoginId(Object loginId) {
    return getTokenValueByLoginId(loginId, null);
}

最后是根据 token 列表的最后一个 token 注销的,所以当用户小明点击退出后,在 Client 应用 Redis 中,属于后来登录用户小红的 token 被清除掉了。

SaToken配置

下方是个人的 SaToken 配置情况,Redis没有单独做数据分离,所以未贴出其配置。

## Server应用Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: satoken
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: false
  # 是否输出操作日志
  is-log: false
  # 是否打印 sa-token Logo
  is-print: false
  # 单点登录相关配置
  sso:
    # Ticket有效期 (单位: 秒),默认五分钟
    ticket-timeout: 300
    # 是否打开单点注销功能
    is-slo: true
    # 是否打开模式三
    isHttp: true
    # 接口调用秘钥(用于SSO模式三的单点注销功能)
    secretkey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
## Client应用Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: satoken
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: false
  # 是否输出操作日志
  is-log: true
  # 是否打印 sa-token Logo
  is-print: true
  # 单点登录相关配置
  sso:
    # SSO-Server端 统一认证地址(前后端分离项目)
    auth-url: http://account.sso-server.com:56438/
    # 使用Http请求校验ticket
    is-http: true
    # SSO-Server端 ticket校验地址
    check-ticket-url: http://account.sso-server.com:9000/sso/checkTicket
    # 打开单点注销功能
    is-slo: true
    # 单点注销地址
    slo-url: http://account.sso-server.com:9000/sso/logout
    # 接口调用秘钥
    secretkey: Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    # SSO-Server端 查询userinfo地址
    userinfo-url: http://account.sso-server.com:9000/sso/userinfo
Charles7c 修改了描述
Charles7c 修改了标题
Charles7c 修改了描述
Charles7c 修改了标题

输入图片说明

这个小伙是你吗

不是我。

输入图片说明

这,,

所以目前SSO模式三的单点注销是属于 bug?还是设计如此?

那这边要解决的话,就是说要获取到对应登录的 token,然后根据 token 来进行调用 logout,而非直接调用咱们的单点注销。

刘潇 任务状态待办的 修改为已完成

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(2)
1294645 charles7c 1672402076 1766140 sz6 1578959462
Java
1
https://gitee.com/dromara/sa-token.git
git@gitee.com:dromara/sa-token.git
dromara
sa-token
Sa-Token

搜索帮助