MaxKey hace 3 años
padre
commit
8fb35b33d6

+ 1 - 1
README_zh.md

@@ -24,7 +24,7 @@
 
 # 概述
 
-<b>MaxKey</b>单点登录认证系统,谐音马克思的钥匙寓意是最大钥匙,是<b>业界领先的IAM身份管理和认证产品</b>,支持OAuth 2.x/OpenID Connect、SAML 2.0、JWT、CAS、SCIM等标准协议,提供<b> 标准、安全和开放</b>的用户身份管理(IDM)、身份认证(AM)、单点登录(SSO)、RBAC权限管理和资源管理等。
+<b>MaxKey</b>单点登录认证系统,谐音马克思的钥匙寓意是最大钥匙,是<b>业界领先的IAM身份管理和认证产品</b>,支持OAuth 2.x/OpenID Connect、SAML 2.0、JWT、CAS、SCIM等标准协议,提供<b>标准、安全和开放</b>的用户身份管理(IDM)、身份认证(AM)、单点登录(SSO)、RBAC权限管理和资源管理等。
 
 官方网站  <a href="https://www.maxkey.top" target="_blank"><b>官网</b></a> |  <a href="https://maxkeytop.gitee.io" target="_blank"><b>官网二线</b></a>
 

+ 86 - 0
maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/web/InstitutionEndpoint.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+
+package org.maxkey.authn.web;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang3.StringUtils;
+import org.maxkey.configuration.ApplicationConfig;
+import org.maxkey.entity.Institutions;
+import org.maxkey.entity.Message;
+import org.maxkey.persistence.repository.InstitutionsRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping(value = "/inst")
+public class InstitutionEndpoint {
+	private static final  Logger _logger = LoggerFactory.getLogger(InstitutionEndpoint.class);
+	
+	public final static String  HEADER_HOST 		= "host";
+	
+	public final static String  HEADER_HOSTNAME 	= "hostname";
+	
+	@Autowired
+	InstitutionsRepository institutionsRepository;
+	
+	@Autowired
+	ApplicationConfig applicationConfig;
+	
+ 	@RequestMapping(value={"/get"}, produces = {MediaType.APPLICATION_JSON_VALUE})
+	public ResponseEntity<?> get(
+			HttpServletRequest request,
+			@RequestHeader("Origin") String originURL,
+			@RequestHeader(HEADER_HOSTNAME) String headerHostName,
+			@RequestHeader(HEADER_HOST) String headerHost) {
+ 		_logger.debug("get Institution" );
+ 		
+		String host = headerHostName;
+		_logger.trace("hostname {}",host);
+		if(StringUtils.isEmpty(host)) {
+			host = headerHost;
+			_logger.trace("host {}",host);
+		}
+		
+		if(StringUtils.isEmpty(host)) {
+			host = applicationConfig.getDomainName();
+			_logger.trace("config domain {}",host);
+		}
+		
+		if(host.indexOf(":")> -1 ) {
+			host = host.split(":")[0];
+			_logger.trace("domain split {}",host);
+		}
+		
+		Institutions inst = institutionsRepository.get(host);
+		if(inst != null) {
+			_logger.debug("inst {}",inst);
+			return new Message<Institutions>(inst).buildResponse();
+		}else {
+			Institutions defaultInst = institutionsRepository.get("1");
+			_logger.debug("default inst {}",inst);
+			return new Message<Institutions>(defaultInst).buildResponse();
+		}
+ 	}
+}

+ 0 - 2
maxkey-core/src/main/java/org/maxkey/web/WebConstants.java

@@ -33,8 +33,6 @@ public class WebConstants {
     public static final  String CURRENT_INST 		= "current_inst";
     
     public final static  String INST_COOKIE_NAME 	= "mxk_inst";
-    
-    public final static  String FRONTEND_BASE_URI 		= "mxk_frontend_base_uri";
 
     // SPRING_SECURITY_SAVED_REQUEST
     public static final  String FIRST_SAVED_REQUEST_PARAMETER 

+ 1 - 6
maxkey-core/src/main/java/org/maxkey/web/WebContext.java

@@ -307,12 +307,7 @@ public final class WebContext {
     
     public static Institutions getInst() {
         return (Institutions)getAttribute(WebConstants.CURRENT_INST);
-    }
-    
-    public static String getBaseUri() {
-        return (String)getAttribute(WebConstants.FRONTEND_BASE_URI);
-    }
-    
+    }    
 
     /**
      * encoding encodingString by ApplicationConfig.

+ 2 - 1
maxkey-core/src/main/java/org/maxkey/web/WebInstRequestFilter.java

@@ -37,7 +37,9 @@ public class WebInstRequestFilter  extends GenericFilterBean {
 	final static Logger _logger = LoggerFactory.getLogger(GenericFilterBean.class);	
 	
 	public final static String  HEADER_HOST 		= "host";
+	
 	public final static String  HEADER_HOSTNAME 	= "hostname";
+	
 	public final static String  HEADER_ORIGIN		= "Origin";	
 	
 	InstitutionsRepository institutionsRepository;
@@ -74,7 +76,6 @@ public class WebInstRequestFilter  extends GenericFilterBean {
 			if(StringUtils.isEmpty(origin)) {
 				origin = applicationConfig.getFrontendUri();
 			}
-			request.getSession().setAttribute(WebConstants.FRONTEND_BASE_URI, origin);
 		}
         chain.doFilter(servletRequest, servletResponse);
 	}

+ 14 - 2
maxkey-web-frontend/maxkey-web-app/src/app/layout/basic/basic.component.ts

@@ -22,6 +22,7 @@ import { environment } from '@env/environment';
 import { CONSTS } from 'src/app/shared/consts';
 
 import { AuthnService } from '../../service/authn.service';
+import { knowHost } from '../../shared/utils/knowhost';
 import { LayoutDefaultOptions } from '../../theme/layout-default';
 
 @Component({
@@ -109,8 +110,9 @@ import { LayoutDefaultOptions } from '../../theme/layout-default';
     <theme-btn></theme-btn>
   `
 })
-export class LayoutBasicComponent {
+export class LayoutBasicComponent implements OnInit {
   version = CONSTS.VERSION;
+  inst: any;
   options: LayoutDefaultOptions = {
     logoExpanded: `./assets/logo-full.svg`,
     logoCollapsed: `./assets/logo.svg`,
@@ -129,5 +131,15 @@ export class LayoutBasicComponent {
   changePassword(): void {
     this.router.navigateByUrl('/config/password');
   }
-  constructor(private settingsService: SettingsService, private router: Router) { }
+
+  ngOnInit(): void {
+    this.inst = this.authnService.getInst();
+    if (this.inst == null) {
+      this.authnService.initInst().subscribe(res => {
+        this.authnService.setInst(res.data, !knowHost());
+        this.inst = this.authnService.getInst();
+      });
+    }
+  }
+  constructor(private authnService: AuthnService, private settingsService: SettingsService, private router: Router) { }
 }

+ 16 - 8
maxkey-web-frontend/maxkey-web-app/src/app/layout/passport/passport.component.html

@@ -1,30 +1,38 @@
 <div class="container">
   <div nz-row style="border-bottom: 1px solid #e5e5e5; min-height: 60px; text-shadow: 0 1px 0 #fff">
     <div nz-col nzMd="2"></div>
-    <div nz-col nzMd="2" style="text-align: right"> <img style="margin-top: 6px" class="logo" src="./assets/logo.jpg" /></div>
+    <div nz-col nzMd="2" style="text-align: right">
+      <img *ngIf="this.inst == null || !inst.custom" style="margin-top: 6px" class="logo" src="./assets/logo.jpg" />
+      <img *ngIf="inst.custom" style="margin-top: 6px" class="logo" src="{{ inst.logo }}" />
+    </div>
     <div nz-col nzMd="10">
-      <div class="title">{{ 'mxk.login.title' | i18n }}{{ 'mxk.title' | i18n }}</div>
+      <div *ngIf="!inst.custom" class="title">Max<span style="color: #ffd700">Key</span>{{ 'mxk.title' | i18n }}</div>
+      <div *ngIf="inst.custom" class="title">{{ inst.title }}</div>
     </div>
     <div nz-col nzMd="6"></div>
-    <div nz-col nzXs="0" nzSm="0" nzMd="2"><header-i18n showLangText="false" class="langs"></header-i18n></div>
+    <div nz-col nzXs="0" nzSm="0" nzMd="2">
+      <header-i18n showLangText="false" class="langs"></header-i18n>
+    </div>
     <div nz-col nzMd="2"></div>
   </div>
 
   <div class="wrap">
     <div class="top" nz-col nzXs="0" nzSm="0" nzMd="24">
-      <div class="desc"
-        ><b>{{ 'mxk.login.title.sub' | i18n }}</b></div
-      >
+      <div class="desc" *ngIf="!inst.custom">
+        <b>{{ 'mxk.login.title.sub' | i18n }}</b>
+      </div>
     </div>
     <router-outlet></router-outlet>
     <global-footer style="border-top: 1px solid #e5e5e5; min-height: 60px; text-shadow: 0 1px 0 #fff">
       <div style="margin-top: 20px">
         MaxKey {{ version }}<br />
         Copyright
-        <i nz-icon nzType="copyright"></i> 2022 <a href="//www.maxkey.top" target="_blank">http://www.maxkey.top</a><br />
+        <i nz-icon nzType="copyright"></i>
+        2022
+        <a href="//www.maxkey.top" target="_blank"> http://www.maxkey.top </a><br />
         Licensed under the Apache License, Version 2.0
       </div>
     </global-footer>
   </div>
 </div>
-<theme-btn></theme-btn>
+<theme-btn></theme-btn>

+ 19 - 2
maxkey-web-frontend/maxkey-web-app/src/app/layout/passport/passport.component.ts

@@ -19,6 +19,9 @@ import { ActivatedRoute } from '@angular/router';
 import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
 import { CONSTS } from 'src/app/shared/consts';
 
+import { AuthnService } from '../../service/authn.service';
+import { knowHost } from '../../shared/utils/knowhost';
+
 @Component({
   selector: 'layout-passport',
   templateUrl: './passport.component.html',
@@ -26,6 +29,8 @@ import { CONSTS } from 'src/app/shared/consts';
 })
 export class LayoutPassportComponent implements OnInit {
   version = CONSTS.VERSION;
+  inst: any;
+
   links = [
     {
       title: '帮助',
@@ -37,7 +42,19 @@ export class LayoutPassportComponent implements OnInit {
     }
   ];
 
-  constructor(@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService, private route: ActivatedRoute) { }
+  constructor(
+    private authnService: AuthnService,
+    @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
+    private route: ActivatedRoute
+  ) { }
 
-  ngOnInit(): void { }
+  ngOnInit(): void {
+    this.inst = this.authnService.getInst();
+    if (this.inst == null) {
+      this.authnService.initInst().subscribe(res => {
+        this.authnService.setInst(res.data, !knowHost());
+        this.inst = this.authnService.getInst();
+      });
+    }
+  }
 }

+ 17 - 4
maxkey-web-frontend/maxkey-web-app/src/app/service/authn.service.ts

@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import { HttpClient } from '@angular/common/http';
 import { Injectable, Inject } from '@angular/core';
 import { Router } from '@angular/router';
 import { StartupService } from '@core';
@@ -37,6 +38,7 @@ export class AuthnService {
     private settingsService: SettingsService,
     private cookieService: CookieService,
     private startupService: StartupService,
+    private client: HttpClient,
     @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
     private http: _HttpClient
   ) { }
@@ -103,13 +105,24 @@ export class AuthnService {
   jwtAuth(authParam: any) {
     return this.http.get(`/login/jwt/trust?_allow_anonymous=true`, authParam);
   }
-
-  setInst(inst: any) {
-    localStorage.setItem(CONSTS.INST, JSON.stringify({ id: inst.id, name: inst.name, title: inst.frontTitle, logo: inst.logo }));
+  setInst(inst: any, custom: boolean) {
+    localStorage.setItem(
+      CONSTS.INST,
+      JSON.stringify({ custom: custom, id: inst.id, name: inst.name, title: inst.frontTitle, logo: inst.logo })
+    );
   }
 
   getInst() {
-    return JSON.parse(`${localStorage.getItem(CONSTS.INST)}`);
+    let strInst = `${localStorage.getItem(CONSTS.INST)}`;
+    if (strInst == null || strInst === '') {
+      return null;
+    } else {
+      return JSON.parse(strInst);
+    }
+  }
+
+  initInst() {
+    return this.http.get(`/inst/get?_allow_anonymous=true`);
   }
 
   setRoles(aclService: ACLService | null): string[] {

+ 9 - 0
maxkey-web-frontend/maxkey-web-app/src/app/shared/utils/knowhost.ts

@@ -0,0 +1,9 @@
+export function knowHost() {
+    let hostArray: string[] = new Array('localhost', 'sso.maxkey.top', 'mgt.maxkey.top', 'sso.maxsso.net', 'mgt.maxsso.net');
+    for (var i = 0; i < hostArray.length; i++) {
+        if (hostArray[i] == location.hostname) {
+            return true;
+        }
+    }
+    return false;
+}

+ 112 - 18
maxkey-web-frontend/maxkey-web-app/src/index.html

@@ -13,7 +13,101 @@
   <script src="./assets/transform.js"></script>
   <!-- Apple Touch Icon -->
   <!-- <link rel="apple-touch-icon" href="custom-icon.png"> -->
-  <style type="text/css">.preloader{position:fixed;top:0;left:0;z-index:9999;width:100%;height:100%;overflow:hidden;background:#49a9ee;transition:opacity .65s}.preloader-hidden-add{display:block;opacity:1}.preloader-hidden-add-active{opacity:0}.preloader-hidden{display:none}.cs-loader{position:absolute;top:0;left:0;width:100%;height:100%}.cs-loader-inner{position:absolute;top:50%;width:100%;color:#fff;text-align:center;transform:translateY(-50%)}.cs-loader-inner label{display:inline-block;font-size:20px;opacity:0}@keyframes lol{0%{transform:translateX(-300px);opacity:0}33%{transform:translateX(0);opacity:1}66%{transform:translateX(0);opacity:1}100%{transform:translateX(300px);opacity:0}}.cs-loader-inner label:nth-child(6){animation:lol 3s infinite ease-in-out}.cs-loader-inner label:nth-child(5){animation:lol 3s .1s infinite ease-in-out}.cs-loader-inner label:nth-child(4){animation:lol 3s .2s infinite ease-in-out}.cs-loader-inner label:nth-child(3){animation:lol 3s .3s infinite ease-in-out}.cs-loader-inner label:nth-child(2){animation:lol 3s .4s infinite ease-in-out}.cs-loader-inner label:nth-child(1){animation:lol 3s .5s infinite ease-in-out}</style>
+  <style type="text/css">
+    .preloader {
+      position: fixed;
+      top: 0;
+      left: 0;
+      z-index: 9999;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      background: #49a9ee;
+      transition: opacity .65s
+    }
+
+    .preloader-hidden-add {
+      display: block;
+      opacity: 1
+    }
+
+    .preloader-hidden-add-active {
+      opacity: 0
+    }
+
+    .preloader-hidden {
+      display: none
+    }
+
+    .cs-loader {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%
+    }
+
+    .cs-loader-inner {
+      position: absolute;
+      top: 50%;
+      width: 100%;
+      color: #fff;
+      text-align: center;
+      transform: translateY(-50%)
+    }
+
+    .cs-loader-inner label {
+      display: inline-block;
+      font-size: 20px;
+      opacity: 0
+    }
+
+    @keyframes lol {
+      0% {
+        transform: translateX(-300px);
+        opacity: 0
+      }
+
+      33% {
+        transform: translateX(0);
+        opacity: 1
+      }
+
+      66% {
+        transform: translateX(0);
+        opacity: 1
+      }
+
+      100% {
+        transform: translateX(300px);
+        opacity: 0
+      }
+    }
+
+    .cs-loader-inner label:nth-child(6) {
+      animation: lol 3s infinite ease-in-out
+    }
+
+    .cs-loader-inner label:nth-child(5) {
+      animation: lol 3s .1s infinite ease-in-out
+    }
+
+    .cs-loader-inner label:nth-child(4) {
+      animation: lol 3s .2s infinite ease-in-out
+    }
+
+    .cs-loader-inner label:nth-child(3) {
+      animation: lol 3s .3s infinite ease-in-out
+    }
+
+    .cs-loader-inner label:nth-child(2) {
+      animation: lol 3s .4s infinite ease-in-out
+    }
+
+    .cs-loader-inner label:nth-child(1) {
+      animation: lol 3s .5s infinite ease-in-out
+    }
+  </style>
 </head>
 
 <body>
@@ -56,28 +150,28 @@
   }
 </script>
 -->
-
 <!--飞书-->
 <!---->
 <script src="http://sf3-cn.feishucdn.com/obj/static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.1.js"></script>
-<script type="text/javascript"> 
+<script type="text/javascript">
   var fsredirectUri = "";
-  var QRLoginObj ;
-  var handleMessage = function (event) {        
-      var origin = event.origin;    
-      // 使用 matchOrigin 方法来判断 message 是否来自飞书页面
-      if( QRLoginObj && QRLoginObj.matchOrigin(origin) ) {           
-          var loginTmpCode = event.data; 
-          // 在授权页面地址上拼接上参数 tmp_code,并跳转
-          fsredirectUri = fsredirectUri+"&tmp_code="+loginTmpCode;
-          window.top.location.href = fsredirectUri;
-      }
+  var QRLoginObj;
+  var handleMessage = function (event) {
+    var origin = event.origin;
+    // 使用 matchOrigin 方法来判断 message 是否来自飞书页面
+    if (QRLoginObj && QRLoginObj.matchOrigin(origin)) {
+      var loginTmpCode = event.data;
+      // 在授权页面地址上拼接上参数 tmp_code,并跳转
+      fsredirectUri = fsredirectUri + "&tmp_code=" + loginTmpCode;
+      window.top.location.href = fsredirectUri;
+    }
   };
-  if (typeof window.addEventListener != 'undefined') {   
-      window.addEventListener('message', handleMessage, false);} 
-  else if (typeof window.attachEvent != 'undefined') { 
-      window.attachEvent('onmessage', handleMessage);
+  if (typeof window.addEventListener != 'undefined') {
+    window.addEventListener('message', handleMessage, false);
+  }
+  else if (typeof window.attachEvent != 'undefined') {
+    window.attachEvent('onmessage', handleMessage);
   }
 </script>
 
-</html>
+</html>