一、需求描述

客户采用oauth2协议集成单点登录

1.1 oauth2 概念解析

OAuth 2.0的运行流程如下图,摘自RFC 6749。

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资
源。

二、具体案例配置

2.2 具体案例1 【v3.4.5+】

客户认证中心为认证中心
把客户的token转换成ibps识别的token,需要后端对接下客户的认证中心

2.2.1、前端代码说明

路由拦截 (@/router/index.js)
原理:路由拦截第三方跳转的地址,转换成平台的token设置token

以下是伪代码,具体看实际代码

 router.beforeEach(async(to, from, next) => {
 //第三方返回的token 拦截
  const query = to.query || {}
   const accessToken = query[SSO_TOKEN_KEY]
  if (util.isNotEmpty(accessToken)) {
   //第三方返回的token 拦截 转换成我们系统的token  存入token
   //TODO:对接下客户的认证中心
  // 更新前端的token缓存
    updateToken({
      access_token: accessToken
    })
  }

 //...其他代码略
 })

2.2.2、具体配置

以下简单配置,具体看实际代码

在config.js 配置, 配置如下:

    // ================ 单点登录=====================
    //开启单点登录
    ENABLE_SSO: true,
    // 单点登录集成方式 url 通过url跳转传递token,iframe:通过iframe集成
    SSO_MODE: 'url',
    // 单点登录token 存储方式 url:通过url传递(iframe方式不支持),cookie:通过cookie传递
    SSO_TOKEN_MODE: 'url',
    // 单点登录token 存储key 
    SSO_TOKEN_KEY: 'access-token',
    //单点登录的登录地址
    SSO_LOGIN_URL: 'http://10.10.4.64:30443/web/login.html?callback_url={{callback_url}}',
    // 退出单点登录的地址
    SSO_LOGOUT_URL: 'http://10.10.4.64:30443/web/login.html?callback_url={{callback_url}}',

2.3 具体案例2【不推荐使用仅参考使用

2.3.1 案例描述:

外部系统通过弹窗内嵌一个iframe打开IBPS表单, 要解决不用登录问题

2.3.2 具体方案

该方案安全性要考虑下,可能会泄漏用户名和密码,不推荐使用,只可以参考使用

方案: IBPS提供一个账号密码, 由外部系统通过JS脚本访问IBPS的登录接口, 拿到合法TOKEN。
拿到TOKEN后,正式打开iframe页面, 携带着TOKEN进行访问IBPS的表单页面。
说明: 自己使用一个静态HTML的方式, 搞一个按钮, 写一下脚本, 点击按钮后访问IBPS登录接口获取合法TOKEN, 再打开一个弹窗内嵌一个iframe页, 访问IBPS的其中某个表单。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <title>模拟登录</title>
</head>
<body>
    <div id="app">
        <div><el-button @click="handleButton">Button</el-button></div>
        <!-- 
            要使用iframe必须将配置文件 public\config.js  中的  STORE_TOKEN_ADAPTER  改为  storage
         -->
        <iframe 
            v-if="visible"
            :id="iframeName"
            :ref="iframeName"
            :name="iframeName"
            title="Inline Frame Example"
            width="800"
            height="800"
            frameborder="0"
            :src="src"
        >
        </iframe>
    </div>
</body>
    <!-- import Vue  -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <!-- import Element ui -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
    new Vue({
        el: '#app',
        data: function() {
            return { 
                iframeName: 'iframeName',
                visible: false,
                CLIENT_ID: '23ef4e3585436167',
                CLIENT_SECRET: '34114e44e3bca741e0ee02d4bc42dfc08ca0b60149daefbacac9b53503f4edeea6e81e8509d5c854',
                GRANT_TYPE: 'authorization_code',
                src: '',
                prefix: 'http://192.168.3.230:45100/ibps/oauth2/v3',
                // prefix: 'http://192.168.3.230:15100/ibps/oauth2/v3',
                pageUrl:'http://192.168.3.240/saas',
                username:"admin", 
                password:"YpHkCVXVuORDcZ1I1MI1clxLwnb8JVPyHq52IeNeLz1RDeStiTTHLeMPFoBfxsGchqGCgiWadOiJ2bS9pB8yog63ikenRJtDGhpyihLtD65Dmj3mlMT2LloAUbnsTNzzNqf1dFF15Gs1NmO2J6IdYHZAemXgq70v7bbH329xj5I="
            }
        },
        mounted(){
            window.addEventListener('message',(event)=>{
                console.info('子页面向父页面传递参数',event)
                this.visible = false
                window.close()
            })
        },
        methods:{
            closeIframe(){
                this.visible = false
            },
            handleButton(){
                this.visible = false
                let data = {
                    remember: true,
                    username:this.username,
                    password: this.password
                }
                $.ajax({
                    type: "post",
                    url:this.prefix+"/user/login/apply",
                    dataType: "json",
                    data: JSON.stringify(data),
                    contentType: "application/json; charset=utf-8",
                    success:(data) => {
                        console.info(data);
                        this.applyOne(data.data);
                    },
                    error: function(err){
                        alert("数据异常!");
                    }

                });
            },
            applyOne(val){
                let url = this.prefix+"/authorize/apply"
                let data = {
                    client_id: this.CLIENT_ID,
                    login_state: val
                }
                $.ajax({
                    type: "post",
                    url:url,

                    dataType: "json",
                    data: JSON.stringify(data),
                    contentType: "application/json; charset=utf-8",
                    success:(data) => {
                        console.info(data);
                        this.applyTwo(data.data)
                    },
                    error: function(err){
                        alert("数据异常!");
                    }

                });
            },
            applyTwo(val){
                let url = this.prefix+"/authentication/apply"
                let data = {
                    authorize_code: val,
                    client_id: this.CLIENT_ID,
                    client_secret: this.CLIENT_SECRET,
                    grant_type: this.GRANT_TYPE
                }
                const pageUrl = this.pageUrl
                $.ajax({
                    type: "post",
                    url:url,

                    dataType: "json",
                    data: JSON.stringify(data),
                    contentType: "application/json; charset=utf-8",
                    success:(data) => {
                        console.info(data);
                        let authorizeUrl = pageUrl+"/#/ysmk/form_cdcsdemo?layout=none&access_token="+data.data.access_token
                        this.openWindow(authorizeUrl, null, 600, 600)
                        this.src = authorizeUrl
                        console.info(this.src)
                        this.visible = true
                    },
                    error: function(err){
                        alert("数据异常!");
                    }

                });
            },
            openWindow(url, title, w, h) {
                // Fixes dual-screen position                            Most browsers       Firefox
                const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
                const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top

                const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width
                const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height

                const left = ((width / 2) - (w / 2)) + dualScreenLeft
                const top = ((height / 2) - (h / 2)) + dualScreenTop
                const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left)

                // Puts focus on the newWindow
                if (window.focus) {
                    newWindow.focus()
                }
            }
        }
    })

    </script>
</html>

三、FAQ

3.1 iframe嵌套谷歌浏览器不行,火狐可以,一直停留到登录页

下述方案任选其一:
方案1、谷歌浏览器不允许cookie,可以把这个存储在localStorage【推荐】
修改平台的配置,把token放在localStorage

方案2、 使用nginx将认证与业务系统、IBPS配置为同一域名或ip
将配置加入 nginx 配置文件,用于代理认证,保证认证与业务系统及IBPS 同一域名

方案3、 浏览器显式关闭该功能【不推荐】

地址栏输入:chrome://flags/

找到SameSite by default cookies和Cookies without SameSite must be secure
将上面两项设置为 Disable

4、 将http协议升级为https,并设置响应头
网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

Set-Cookie: Path:/; SameSite=None; Secure

文档更新时间: 2023-10-25 10:18   作者:hugh