Pārlūkot izejas kodu

4.0.23 增加单点登录功能

liuyanpeng 2 gadi atpakaļ
vecāks
revīzija
92d15b750d

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "name": "client-base-v4",
   "description": "Leanwo Prodog Client",
-  "version": "4.0.22",
+  "version": "4.0.23",
   "author": "yangzhijie1488 <yangzhijie1488@163.com>",
   "scripts": {
     "dev": "cross-env webpack serve --config ./webpack.dev.js",

+ 21 - 0
src/client/Login.vue

@@ -190,6 +190,7 @@
                         :key="item.id"
                         v-tooltip.top="`${item.authType}-${item.name}`"
                         class="login-third-item"
+                        @click="existsSAML(item.id)"
                       >
                         <img :src="getImageSrc(item.className, item.logo)" />
                       </span>
@@ -333,6 +334,26 @@ export default {
         },
       });
     },
+    // 判断SAML服务器是否存在
+    existsSAML: function (id) {
+      $.ajax({
+        url: '/api/SamlLogin/samlServiceProviderCheck?authSettingId=1',
+        type: 'get',
+        contentType: 'application/json',
+        dataType: 'json',
+        success: function (response) {
+          if (response.errorCode === 0) {
+            let url = '/api/SamlLogin/index?authSettingId=1';
+            window.location.href = url;
+          } else {
+            Notify.error('错误', response.errorMessage);
+          }
+        },
+        error: function (XMLHttpRequest, textStatus, errorThrown) {
+          Common.processException(XMLHttpRequest, textStatus, errorThrown);
+        },
+      });
+    },
 
     /**
      * 发送验证码

+ 225 - 0
src/client/SamlLogin.vue

@@ -0,0 +1,225 @@
+<template>
+  <div id="loginApp">
+    <div id="login_box">
+      <h2>单点登录</h2>
+      <div class="selectItem" style="margin-top: 30px">
+        <label for="">选择登录端:</label>
+        <select v-model="internet" name="language-choice" class="form-control">
+          <option value="pc">电脑端</option>
+          <option value="mobile">移动端</option>
+          <option value="propass">Propass</option>
+        </select>
+      </div>
+      <div class="selectItem">
+        <label>选择登录语言:</label>
+        <select
+          v-model="languageSelected"
+          name="language-choice"
+          class="form-control"
+        >
+          <option value="zh-CN">中文</option>
+          <option value="en-US">English</option>
+        </select>
+      </div>
+      <button class="confirmBtn" @click="azureSamlLogin">确认</button><br />
+    </div>
+  </div>
+</template>
+
+<script>
+import Common from '../common/Common.js';
+import LoginService from './LoginService.js';
+
+export default {
+  data: function () {
+    return {
+      internet: 'pc',
+      languageSelected: 'zh-CN',
+    };
+  },
+  methods: {
+    // 单点登录
+    azureSamlLogin: function () {
+      let _self = this;
+      const params = {
+        languageId: _self.languageSelected,
+      };
+      $.ajax({
+        url: '/api/SamlLogin/login',
+        type: 'post',
+        async: true,
+        data: params,
+        success: function (loginInfoData) {
+          if (loginInfoData.errorCode == 0) {
+            if (_self.internet === 'pc') {
+              LoginService.setLoginInfo(loginInfoData.data, _self.$router);
+              LoginService.saveLocalStorage(_self);
+              _self.setTokenClient(loginInfoData.data.token);
+              localStorage.setItem('allowSound', false);
+              window.location.href =
+                Common.getRootPath() + '/#/desktop/dashboard';
+            } else if (_self.internet === 'mobile') {
+              var loginInfo = loginInfoData.data;
+              Common.clearLocalStorage();
+              Common.clearAppCookie(loginInfo);
+              _self.setAppLocalStorage(loginInfo);
+              _self.setTokenClient();
+              window.location.href =
+                Common.getRootPath() + '/app.html#/desktop/moduleSelect';
+            } else {
+              Common.clearLocalStorage();
+              $.removeCookie('token', { path: '/' });
+              $.removeCookie('token', { path: '/pcapp' });
+              $.cookie('token', loginInfoData.data.token, {
+                expires: 7,
+                path: '/',
+              });
+              _self.setPropassLocalStorage(loginInfoData);
+              window.location.href =
+                Common.getRootPath() + '/propass.html#/propassapp/menu';
+            }
+          } else {
+            Notify.error(
+              _self.$t('lang.login.loginFailure'),
+              loginInfoData.errorMessage,
+            );
+          }
+        },
+        error: function (XMLHttpRequest, textStatus, errorThrown) {
+          Common.processException(XMLHttpRequest, textStatus, errorThrown);
+        },
+      });
+    },
+
+    // 设置 App localStorage
+    setAppLocalStorage: function (loginInfo) {
+      const _self = this;
+      if (!window.localStorage) {
+        Notify.error('错误', '浏览器不支持localstorage', false);
+      } else {
+        const storage = window.localStorage;
+        const loginInfo1 = JSON.stringify(loginInfo);
+        storage.setItem('#loginInfo', loginInfo1);
+        storage.setItem('account', loginInfo.accountId);
+        storage.setItem('token', loginInfo.token);
+        storage.setItem('rememberPassword', false);
+        localStorage.setItem('#languageSelected', _self.languageSelected);
+      }
+    },
+    //  设置 Propass localStorage
+    setPropassLocalStorage: function (loginInfo) {
+      const _self = this;
+
+      const loginInfoStr = JSON.stringify(loginInfo);
+      localStorage.setItem('json_LoginInfo', loginInfoStr);
+      localStorage.setItem('#token', loginInfo.data.token);
+      localStorage.setItem('#accountId', loginInfo.data.accountId);
+      localStorage.setItem('#languageSelected', _self.languageSelected);
+      localStorage.setItem('rememberPassword', false);
+    },
+    //设置 TokenClient
+    setTokenClient: function () {
+      const _self = this;
+      $.ajax({
+        url: '/api/TokenClientResource/saveTokenClient',
+        type: 'post',
+        dataType: 'json',
+        contentType: 'application/json',
+        beforeSend: function (request) {
+          if (_self.internet === 'pc') {
+            request.setRequestHeader('token', localStorage.getItem('#token'));
+          } else {
+            let token = $.cookie('token');
+            let account = $.cookie('account');
+            if (token == undefined) {
+              const localStorageToken = localStorage.getItem('token');
+              if (localStorageToken != undefined) {
+                token = localStorageToken;
+              }
+            }
+            if (account == undefined) {
+              const localStorageAccount = localStorage.getItem('account');
+              if (localStorageAccount != undefined) {
+                account = localStorageAccount;
+              }
+            }
+            request.setRequestHeader('account', account);
+            request.setRequestHeader('token', token);
+          }
+        },
+        success: function (baseObjectResponse) {
+          console.log(baseObjectResponse, 'token添加成功');
+        },
+        error: function (XMLHttpRequest, textStatus, errorThrown) {
+          Common.processException(XMLHttpRequest, textStatus, errorThrown);
+        },
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+body {
+  background: #f7f7f7;
+}
+#login_box {
+  width: 40%;
+  height: 400px;
+  margin: auto;
+  margin-top: 4%;
+  text-align: center;
+  border-radius: 10px;
+  padding: 50px 50px;
+  border: 1px solid #dddddd;
+}
+
+h2 {
+  margin-top: 5%;
+}
+.selectItem {
+  display: flex;
+  align-items: center;
+  margin: 16px 0 16px 0;
+}
+.selectItem > label {
+  width: 100px;
+  text-align: center;
+  font-size: 12px !important;
+  color: rgba(0, 0, 0, 0.4);
+}
+.confirmBtn {
+  margin-top: 50px;
+  width: 60%;
+  height: 30px;
+  border-radius: 6px;
+  border: 1px solid #e6e6e6;
+  text-align: center;
+  line-height: 30px;
+  font-size: 15px;
+  color: #333;
+  background-color: #f7f7f7;
+  text-decoration: none;
+  font-weight: 400;
+  user-select: none;
+}
+.confirmBtn:hover {
+  background-color: #eaeaea;
+  border-color: #adadad;
+  background-position: 0 -15px;
+}
+.form-control {
+  width: 74%;
+  display: block;
+  width: 75%;
+  height: 34px;
+  padding: 6px 12px;
+  font-size: 14px;
+  line-height: 1.42857143;
+  color: #555555;
+  background-color: #fff;
+  background-image: none;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+</style>

+ 14 - 1
src/common/Common.js

@@ -297,7 +297,20 @@ export default {
       }
     }
   },
-
+  // 清空 Cookie
+  clearAppCookie: function (loginInfo) {
+    document.cookie.split(';').forEach(function (c) {
+      document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
+    });
+    $.removeCookie('token', { path: '/' });
+    $.removeCookie('token', { path: '/app' });
+    $.cookie('token', loginInfo.token, {
+      expires: 7,
+      path: '/',
+      secure: true,
+      sameSite: 'Strict',
+    });
+  },
   clearLocalStorage: function () {
     // 清理localStorage时需要保留的参数列表
     var reserveParams = ['hostPageBaseURL', 'workShopId', 'resourceInstanceId',

+ 129 - 3
src/identity/CreateIdentity.vue

@@ -42,6 +42,7 @@
           autocomplete="off"
         >
           <a-form-item
+            has-feedback
             label="认证源名称"
             name="name"
             :rules="[{ required: true, message: '请输入认证源名称!' }]"
@@ -73,7 +74,11 @@
               <a style="margin-left: 6px" @click="deleteLogo">删除</a>
             </span>
           </a-form-item>
-          <a-form-item label="认证源描述" name="description" style="margin-top: 6px;">
+          <a-form-item
+            label="认证源描述"
+            name="description"
+            style="margin-top: 6px"
+          >
             <a-textarea
               v-model:value="identityInfo.description"
               :rows="4"
@@ -91,6 +96,7 @@
           autocomplete="off"
         >
           <a-form-item
+            has-feedback
             label="身份提供商 id"
             name="entityID"
             :rules="[{ required: true, message: '请输入身份提供商 id!' }]"
@@ -101,6 +107,7 @@
             />
           </a-form-item>
           <a-form-item
+            has-feedback
             label="SSO 地址"
             name="ssoUrl"
             :rules="[{ required: true, message: '请输入 SSO 地址' }]"
@@ -111,6 +118,7 @@
             />
           </a-form-item>
           <a-form-item
+            has-feedback
             label="证书"
             name="certificate"
             :rules="[{ required: true, message: '请输入证书' }]"
@@ -122,7 +130,11 @@
               @blur="certificateBlur"
             />
           </a-form-item>
-          <a-form-item label="退出 URL" name="loginOutUrl">
+          <a-form-item
+            label="退出 URL"
+            name="loginOutUrl"
+            style="margin-top: 10px"
+          >
             <a-input
               v-model:value="identitySetting.loginOutUrl"
               placeholder="非必填,请输入退出 URL"
@@ -144,6 +156,14 @@
               placeholder="选填,IDP 中用户ID"
             />
           </a-form-item>
+          <a-form-item label="clientId(公司ID)" name="clientId">
+            <a-input-number
+              v-model:value="identitySetting.clientId"
+              :controls="false"
+              style="width: 100%"
+              placeholder="选填,IDP 中公司ID"
+            />
+          </a-form-item>
           <a-form-item label="userName(用户姓名)" name="userName">
             <a-input
               v-model:value="identitySetting.userName"
@@ -181,6 +201,46 @@
             />
           </a-form-item>
         </a-form>
+        <div class="proDog-setting">
+          <h3>Prodog 配置</h3>
+          <a-form
+            name="basic"
+            :label-col="{ style: { width: '186px' } }"
+            :wrapper-col="wrapperCol"
+            :rules="rules"
+            :model="identitySetting"
+            autocomplete="off"
+          >
+            <a-form-item has-feedback label="Prodog 实体ID" name="spEntityID">
+              <a-input
+                v-model:value="identitySetting.spEntityID"
+                placeholder="必填, Prodog 实体 ID "
+              />
+            </a-form-item>
+            <a-form-item
+              has-feedback
+              label="Prodog断言解析地址"
+              name="spAssertionConsumeService"
+            >
+              <a-input
+                v-model:value="identitySetting.spAssertionConsumeService"
+                placeholder="必填,Prodog 断言解析地址"
+              />
+            </a-form-item>
+            <a-form-item
+              has-feedback
+              label="Prodog断言解析成功跳转地址"
+              name="spAssertionConsumeSuccessRedirectService"
+            >
+              <a-input
+                v-model:value="
+                  identitySetting.spAssertionConsumeSuccessRedirectService
+                "
+                placeholder="必填,Prodog 断言解析成功跳转地址"
+              />
+            </a-form-item>
+          </a-form>
+        </div>
       </div>
     </div>
     <div class="steps-action">
@@ -226,6 +286,9 @@
       <a-button
         v-if="current == steps.length - 1"
         type="primary"
+        :disabled="
+          !identitySetting.spEntityID || service || redirect ? true : false
+        "
         style="margin-left: 8px"
         @click="createIdentity"
       >
@@ -277,8 +340,14 @@ const identitySetting = ref({
   email: '',
   phoneNumber: '',
   roleTemplateNo: '',
+  spEntityID: 'com.leanwo.prodog.sp',
+  spAssertionConsumeService: 'http://xxxx:xx/api/saml/sso/${id}',
+  spAssertionConsumeSuccessRedirectService:
+    'http://xxxx:xx/index.html/#/samlLogin',
 });
 
+const service = ref(false);
+const redirect = ref(false);
 const logoName = ref('');
 const logoClassName = ref('');
 // 设置form样式
@@ -290,6 +359,55 @@ const labelCol = ref({
 const wrapperCol = ref({
   span: 8,
 });
+// 验证断言解析地址结束字符是否正确
+let validateService = async (_rule, value) => {
+  if (!value) {
+    return Promise.reject('请输入 Prodog 断言解析地址');
+  }
+  if (!value.endsWith('/api/saml/sso/${id}')) {
+    service.value = true;
+    return Promise.reject('断言解析地址必须以/api/saml/sso/${ id }结束');
+  } else {
+    service.value = false;
+  }
+};
+// 验证断言解析成功跳转地址结束字符是否正确
+let redirectService = async (_rule, value) => {
+  if (!value) {
+    return Promise.reject('请输入 Prodog 断言解析成功跳转地址');
+  }
+  if (!value.endsWith('index.html/#/samlLogin')) {
+    redirect.value = true;
+    return Promise.reject(
+      '断言解析成功跳转地址必须以index.html/#/samlLogin结束',
+    );
+  } else {
+    redirect.value = false;
+  }
+};
+
+const rules = {
+  spEntityID: [
+    {
+      required: true,
+      message: '请输入 Prodog 实体 ID',
+    },
+  ],
+  spAssertionConsumeService: [
+    {
+      required: true,
+      validator: validateService,
+      trigger: 'change',
+    },
+  ],
+  spAssertionConsumeSuccessRedirectService: [
+    {
+      required: true,
+      validator: redirectService,
+      trigger: 'change',
+    },
+  ],
+};
 
 // 获取更新Id
 onMounted(() => {
@@ -548,7 +666,15 @@ const setActiveItem = index => {
   margin-top: 8px;
   color: #666;
 }
-:deep(.ant-form-item-label > label){
+:deep(.ant-form-item-label > label) {
+  font-size: 12px !important;
+  color: rgba(0, 0, 0, 0.4);
+}
+.ant-form-item {
+  margin-bottom: 4px;
+}
+.proDog-setting > h3 {
   font-size: 14px !important;
+  font-weight: 700;
 }
 </style>

+ 6 - 1
src/identity/IdentityManager.vue

@@ -80,8 +80,13 @@ const queryAllAuthSetting = () => {
   queryAllAuth().then(
     success => {
       if (success.errorCode === 0) {
-        identityDatas.value = success.datas;
+        if(success.datas && success.datas.length > 0){
+          identityDatas.value = success.datas;
+        } else {
+          identityDatas.value = [];
+        }
       } else {
+        identityDatas.value = [];
         message.warning(success.errorMessage);
       }
     },

+ 2 - 1
src/index.js

@@ -74,7 +74,7 @@ import DataArchive from '../src/archive/DataArchive.vue';
 import ArchivalRecord from '../src/archive/ArchivalRecord.vue';
 import IdentityManager  from '../src/identity/IdentityManager.vue';
 import CreateIdentity  from '../src/identity/CreateIdentity.vue';
-
+import SamlLogin  from './client/SamlLogin.vue';
 
 
 export {
@@ -85,6 +85,7 @@ export {
   Common,
   PushMessage,
   Login,
+  SamlLogin,
   Desktop,
   LowcodePage,
   Dashboard,

+ 8 - 0
src/routes/main_routes.js

@@ -64,6 +64,7 @@ const ArchivalRecord = () => import('../archive/ArchivalRecord.vue');
 const ExcelImport = () => import('../customer/ExcelImport.vue');
 const IdentityManager = () => import('../identity/IdentityManager.vue');
 const CreateIdentity = () => import('../identity/CreateIdentity.vue');
+const SamlLogin = () => import('../client/SamlLogin.vue');
 
 import { ProcessReport } from 'pc-component-v3';
 
@@ -76,6 +77,13 @@ export default [
       loginRequired: false,
     },
   },
+  {
+    path: '/samlLogin',
+    component: SamlLogin,
+    meta: {
+      loginRequired: false,
+    },
+  },
   {
     path: '/desktop', component: Desktop,
     children: [