3

keycloak~对接login-status-iframe页面判断用户状态变更 - 张占岭

 8 months ago
source link: https://www.cnblogs.com/lori/p/17925956.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

上次我们说了,keycloak的login-status-iframe页面的作用,并解决了跨域情况下,iframe与主页面数据传递的方法,这一次,我们主要分析login-status-iframe.html这个文件的源码,然后分析在我们系统中如何与这个页面对接。

login-status-iframe.html源码

<script>
    var init;

    function checkState(clientId, origin, sessionState, callback) {
        var cookie = getCookie();

        var checkCookie = function() {
            if (clientId === init.clientId && origin === init.origin) {
                var c = cookie.split('/');
                if (sessionState === c[2]) {
                    callback('unchanged');
                } else {
                    callback('changed');
                }
            } else {
                callback('error');
            }
        }

        if (!init) {
            var req = new XMLHttpRequest();

            var url = location.href.split("?")[0] + "/init";
            url += "?client_id=" + encodeURIComponent(clientId);
            url += "&origin=" + encodeURIComponent(origin);

            req.open('GET', url, true);

            req.onreadystatechange = function () {
                if (req.readyState === 4) {
                    if (req.status === 204 || req.status === 1223) {
                        init = {
                            clientId: clientId,
                            origin: origin
                        }
                        if (!cookie) {
                            if (sessionState != '') {
                                callback('changed');
                            } else {
                                callback('unchanged');
                            }
                        } else {
                            checkCookie();
                        }
                    } else {
                        callback('error');
                    }
                }
            };

            req.send();
        } else  if (!cookie) {
            if (sessionState != '') {
                callback('changed');
            } else {
                callback('unchanged');
            }
        } else {
            checkCookie();
        }
    }

    function getCookie()
    {
        var cookie = getCookieByName('KEYCLOAK_SESSION');
        if (cookie === null) {
            return getCookieByName('KEYCLOAK_SESSION_LEGACY');
        }
        return cookie;
    }

    function getCookieByName(name)
    {
        name = name + '=';
        var ca = document.cookie.split(';');
        for(var i=0; i<ca.length; i++)
        {
            var c = ca[i].trim();
            if (c.indexOf(name)===0) return c.substring(name.length,c.length);
        }
        return null;
    }

    function receiveMessage(event)
    {
        if (typeof event.data !== 'string') {
            return
        }

        var origin = event.origin;
        var data = event.data.split(' ');
        if (data.length != 2) {
            return;
        }

        var clientId = data[0];
        var sessionState = data[1];

        checkState(clientId, event.origin, sessionState, function(result) {
            event.source.postMessage(result, origin);
        });
    }

    window.addEventListener("message", receiveMessage, false);
</script>

具体方法说明

这个页面主要由以下4个方法组成,下面分别去介绍

  1. checkState(clientId, origin, sessionState, callback) 检查当前浏览器上,用户在keycloak登录的状态
  2. getCookie() 获取cookie中存储的用户状态
  3. getCookieByName(name) 获取指定key的cookie值
  4. receiveMessage(event) 接收从父页面通过postMessage传过来的消息

getCookieByName方法

按着指定向名称,从浏览器的cookie中获取,这是所有可见cookie的字符串,每对使用分号分开,咱们这个方法是返回了某个key对应的具体value.

getCookie方法

获取KEYCLOAK_SESSION的值,如果它不存在,就获取KEYCLOAK_SESSION_LEGACY的值,之所以有KEYCLOAK_SESSION_LEGACY,主要是考虑到了浏览器的兼容性问题。

receiveMessage方法

这个方法主要是用来接收主页面发过来的数据,然后进行状态检查的,子页面通过window.addEventListener("message", receiveMessage, false);来进行事件监听,主页面会提供client_id和sesssion_state,并使用空格将两个参数分开,然后在回调方法里,会向主页面进行通知,通过event.source.postMessage(result, origin);实现,result表示通知的内容,origin表示主页面的域名,其实这是为了安全考虑的,在主页面收到消息后,也会判断这个origin,会判断是否从子页面的域名;所以这个origin其实是事件的发起者的域名。

checkState方法

这是整个login-status-iframe.html页面的核心方法,主要用来判断登录状态,下面分步骤说一下:

  1. 它首先会从浏览器cookie中拿出KEYCLOAK_SESSION的值,它和主页面传过来的值做对比,如果相同状态就没改变(可能已登录或者未登录),如果不相同,如果说明状态改变了;
  2. 判断init是否初始化,如果没有,就从login-status-iframe.html/init?client_id=...这个keycloak的接口中,异步获取登录状态写入浏览器cookie,然后再用步骤1做状态判断;
  3. 如果init已经完成初始化,就直接检查cookie中的用户状态

主页面对接login-status-iframe.html页面

<iframe src="https://kc.com/auth/realms/{you}/protocol/openid-connect/login-status-iframe.html"
                    id="keycloak-status-iframe" style="display:none"></iframe>
<script>
 function getQueryVariable(variable) {
      var query = window.location.search.substring(1);
      var vars = query.split("&");
      for (var i = 0; i < vars.length; i++) {
          var pair = vars[i].split("=");
          if (pair[0] == variable) {
              return pair[1];
          }
      }
      return (false);
  }

  var iframe = document.getElementById('keycloak-status-iframe');
  iframe.onload = function () {
      var val = "democlient ";
      if (getQueryVariable("code")!="" && getQueryVariable("code").split(".").length == 3) {
          val = "democlient " + getQueryVariable("code").split(".")[1];//这里向iframe传的参数是"client_id session_state"
      }
      iframe.contentWindow.postMessage(val, 'https://kc.com');
      window.addEventListener('message', function (event) {
          if (event.origin !== 'https://kc.com') {
              return;
          }
          if (event.data === 'unchanged') {
              alert('用户会话未发生变化');
          } else if (event.data === 'changed') {
              alert('用户会话状态发生变化,可能已经注销');
          }
      }, false);


  }
</script>

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK