This commit is contained in:
MaxKey 2022-05-17 16:10:58 +08:00
parent 5a97385c50
commit 8fb35b33d6
11 changed files with 277 additions and 44 deletions

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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.

View File

@ -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);
}

View File

@ -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) { }
}

View File

@ -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>

View File

@ -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();
});
}
}
}

View File

@ -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[] {

View File

@ -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;
}

View File

@ -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>