♻️ 重构代码

pull/12/MERGE
smallbun 2023-02-08 14:44:21 +08:00
parent 1a1134b79d
commit 6afe10a1bd
532 changed files with 9835 additions and 4015 deletions

View File

@ -22,10 +22,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import cn.topiam.employee.application.AbstractApplicationService;
import cn.topiam.employee.application.CasApplicationService;
import cn.topiam.employee.application.cas.model.CasSsoModel;
import cn.topiam.employee.common.entity.app.po.AppCasConfigPO;
import cn.topiam.employee.common.repository.app.*;
import cn.topiam.employee.core.protocol.CasSsoModel;
/**
* CAS
@ -58,8 +57,8 @@ public abstract class AbstractCasApplicationService extends AbstractApplicationS
@Override
public CasSsoModel getSsoModel(Long appId) {
AppCasConfigPO appCasConfigPO = appCasConfigRepository.getByAppId(appId);
return CasSsoModel.builder().ssoCallbackUrl(appCasConfigPO.getSpCallbackUrl()).build();
AppCasConfigPO appCasConfigPo = appCasConfigRepository.getByAppId(appId);
return CasSsoModel.builder().clientServiceUrl(appCasConfigPo.getClientServiceUrl()).build();
}
/**

View File

@ -1,5 +1,5 @@
/*
* eiam-application-core - Employee Identity and Access Management Program
* eiam-application-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,9 +15,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application;
package cn.topiam.employee.application.cas;
import cn.topiam.employee.core.protocol.CasSsoModel;
import cn.topiam.employee.application.ApplicationService;
import cn.topiam.employee.application.cas.model.CasSsoModel;
/**
* @author TopIAM

View File

@ -28,26 +28,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import cn.topiam.employee.application.cas.model.AppCasStandardConfigGetResult;
import cn.topiam.employee.application.cas.model.AppCasStandardSaveConfigParam;
import cn.topiam.employee.application.cas.converter.AppCasStandardConfigConverter;
import cn.topiam.employee.application.cas.pojo.AppCasStandardSaveConfigParam;
import cn.topiam.employee.application.exception.AppNotExistException;
import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.common.entity.app.AppCasConfigEntity;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.po.AppCasConfigPO;
import cn.topiam.employee.common.enums.app.AppProtocol;
import cn.topiam.employee.common.enums.app.AppType;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import cn.topiam.employee.common.enums.app.*;
import cn.topiam.employee.common.repository.app.*;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.validation.ValidationHelp;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
/**
* Cas
*
@ -68,10 +61,12 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
AppAccountRepository appAccountRepository,
AppAccessPolicyRepository appAccessPolicyRepository,
AppRepository appRepository,
AppCasConfigRepository appCasConfigRepository) {
AppCasConfigRepository appCasConfigRepository,
AppCasStandardConfigConverter casStandardConfigConverter) {
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository,
appCasConfigRepository);
this.appCasConfigRepository = appCasConfigRepository;
this.casStandardConfigConverter = casStandardConfigConverter;
}
/**
@ -119,7 +114,8 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
throw new AppNotExistException();
}
AppCasConfigEntity entity = cas.get();
entity.setSpCallbackUrl(model.getSpCallbackUrl());
entity.setClientServiceUrl(model.getClientServerUrl());
entity.setUserIdentityType(model.getUserIdentityType());
appCasConfigRepository.save(entity);
}
@ -133,18 +129,7 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
@Override
public Object getConfig(String appId) {
AppCasConfigPO po = appCasConfigRepository.getByAppId(Long.valueOf(appId));
AppCasStandardConfigGetResult result = new AppCasStandardConfigGetResult();
result.setAuthorizationType(po.getAuthorizationType());
result.setInitLoginType(po.getInitLoginType());
result.setInitLoginUrl(po.getInitLoginUrl());
result.setSpCallbackUrl(po.getSpCallbackUrl());
String baseUrl = ServerContextHelp.getPortalPublicBaseUrl();
// 服务端URL配置前缀
result.setServerUrlPrefix(
baseUrl + ProtocolConstants.CasEndpointConstants.CAS_AUTHORIZE_BASE_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
return result;
return casStandardConfigConverter.entityConverterToCasConfigResult(po);
}
/**
@ -236,16 +221,18 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
appEntity.setProtocol(getProtocol());
appEntity.setClientId(idGenerator.generateId().toString().replace("-", ""));
appEntity.setClientSecret(idGenerator.generateId().toString().replace("-", ""));
appEntity.setInitLoginType(InitLoginType.PORTAL_OR_APP);
appEntity.setInitLoginType(InitLoginType.APP);
appEntity.setAuthorizationType(AuthorizationType.AUTHORIZATION);
appEntity.setRemark(remark);
appRepository.save(appEntity);
AppCasConfigEntity casEntity = new AppCasConfigEntity();
casEntity.setAppId(appEntity.getId());
casEntity.setSpCallbackUrl("");
casEntity.setUserIdentityType(CasUserIdentityType.USER_USERNAME);
appCasConfigRepository.save(casEntity);
return appEntity.getId().toString();
}
private final AppCasStandardConfigConverter casStandardConfigConverter;
}

View File

@ -19,6 +19,13 @@ package cn.topiam.employee.application.cas.converter;
import org.mapstruct.Mapper;
import cn.topiam.employee.application.cas.pojo.AppCasProtocolEndpoint;
import cn.topiam.employee.application.cas.pojo.AppCasStandardConfigGetResult;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.common.entity.app.po.AppCasConfigPO;
import cn.topiam.employee.core.context.ServerContextHelp;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
/**
*
*
@ -27,4 +34,40 @@ import org.mapstruct.Mapper;
*/
@Mapper(componentModel = "spring")
public interface AppCasStandardConfigConverter {
/**
* CAS
*
* @param po {@link AppCasConfigPO}
* @return {@link AppCasStandardConfigGetResult}
*/
default AppCasStandardConfigGetResult entityConverterToCasConfigResult(AppCasConfigPO po) {
AppCasStandardConfigGetResult result = new AppCasStandardConfigGetResult();
result.setAuthorizationType(po.getAuthorizationType());
result.setInitLoginType(po.getInitLoginType());
result.setInitLoginUrl(po.getInitLoginUrl());
result.setClientServiceUrl(po.getClientServiceUrl());
result.setUserIdentityType(po.getUserIdentityType());
//封装端点信息
AppCasProtocolEndpoint protocolEndpoint = new AppCasProtocolEndpoint();
String baseUrl = ServerContextHelp.getPortalPublicBaseUrl();
protocolEndpoint
.setCasSsoEndpoint(baseUrl + ProtocolConstants.CasEndpointConstants.CAS_LOGIN_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
protocolEndpoint
.setCasSloEndpoint(baseUrl + ProtocolConstants.CasEndpointConstants.CAS_LOGOUT_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
protocolEndpoint.setCasValidateEndpoint(
baseUrl + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V1_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
protocolEndpoint.setCasValidateV2Endpoint(
baseUrl + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V2_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
protocolEndpoint.setCasValidateV3Endpoint(
baseUrl + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V3_PATH
.replace(APP_CODE_VARIABLE, po.getAppCode()));
result.setProtocolEndpoint(protocolEndpoint);
return result;
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-application-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.protocol;
package cn.topiam.employee.application.cas.model;
import java.io.Serializable;
@ -30,6 +30,9 @@ import lombok.Data;
@Builder
public class CasSsoModel implements Serializable {
private String ssoCallbackUrl;
/**
* URL
*/
private String clientServiceUrl;
}

View File

@ -0,0 +1,69 @@
/*
* eiam-application-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.cas.pojo;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/6/4 23:37
*/
@Data
@Schema(description = "协议端点")
public class AppCasProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = -2261602995152894964L;
/**
* CAS SSO
*/
@Schema(description = "CAS SSO 端点")
private String casSsoEndpoint;
/**
* CAS SLO
*/
@Schema(description = "CAS SLO 端点")
private String casSloEndpoint;
/**
* CAS
*/
@Schema(description = "CAS 校验端点")
private String casValidateEndpoint;
/**
* CAS v2
*/
@Schema(description = "CAS V2 校验端点")
private String casValidateV2Endpoint;
/**
* CAS v3
*/
@Schema(description = "CAS V3 校验端点")
private String casValidateV3Endpoint;
}

View File

@ -15,14 +15,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.cas.model;
package cn.topiam.employee.application.cas.pojo;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.CasUserIdentityType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
@ -37,28 +37,35 @@ public class AppCasStandardConfigGetResult {
* ID
*/
@Schema(description = "授权类型")
private AuthorizationType authorizationType;
private AuthorizationType authorizationType;
/**
* SSO
*/
@Schema(description = "SSO 发起登录类型")
private InitLoginType initLoginType;
private InitLoginType initLoginType;
/**
* SSO URL
*/
@Schema(description = "SSO 发起登录URL")
private String initLoginUrl;
private String initLoginUrl;
/**
* SP
* URL
*/
@Parameter(name = "单点登录 sp Callback Url")
private String spCallbackUrl;
@Schema(name = "客户端服务URL")
private String clientServiceUrl;
/**
* Server
*
*/
private String serverUrlPrefix;
@Schema(name = "用户身份类型标识")
private CasUserIdentityType userIdentityType;
/**
* CAS
*/
@Schema(name = "CAS 协议端点")
private AppCasProtocolEndpoint protocolEndpoint;
}

View File

@ -15,17 +15,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.cas.model;
package cn.topiam.employee.application.cas.pojo;
import java.io.Serial;
import java.io.Serializable;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.CasUserIdentityType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
@ -35,29 +35,35 @@ import io.swagger.v3.oas.annotations.media.Schema;
@Data
public class AppCasStandardSaveConfigParam implements Serializable {
@Serial
private static final long serialVersionUID = 1881187724713984421L;
private static final long serialVersionUID = 1881187724713984421L;
/**
* ID
*/
@Schema(description = "授权类型")
private AuthorizationType authorizationType;
private AuthorizationType authorizationType;
/**
* SSO
*/
@Schema(description = "SSO 发起登录类型")
private InitLoginType initLoginType;
private InitLoginType initLoginType;
/**
* SSO URL
*/
@Schema(description = "SSO 发起登录URL")
private String initLoginUrl;
private String initLoginUrl;
/**
* CAS
*/
@Schema(name = "CAS 用户身份类型")
private CasUserIdentityType userIdentityType;
/**
* SP
*/
@Parameter(name = "单点登录 sp Callback Url")
private String spCallbackUrl;
@Schema(name = "客户端服务URL")
private String clientServerUrl;
}

View File

@ -57,7 +57,7 @@ public abstract class AbstractApplicationService implements ApplicationService {
*
*
* @param appId {@link Long}
* @param appCode {@link Long}
* @param appCode {@link Long}
* @param usingType {@link AppCertUsingType}
*/
public void createCertificate(Long appId, String appCode, AppCertUsingType usingType) {
@ -120,7 +120,7 @@ public abstract class AbstractApplicationService implements ApplicationService {
protected final AppAccountRepository appAccountRepository;
/**
* AppAccessPolicyRepository
*AppAccessPolicyRepository
*/
protected final AppAccessPolicyRepository appAccessPolicyRepository;

View File

@ -44,6 +44,7 @@ public class ApplicationServiceLoader implements ApplicationContextAware {
*
*/
private Map<String, ApplicationService> loadMap = new HashMap<>(16);
private ApplicationContext applicationContext;
/**
* key: codevaluetemplateImpl
*/
@ -64,9 +65,9 @@ public class ApplicationServiceLoader implements ApplicationContextAware {
* @see BeanInitializationException
*/
@Override
public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext) throws BeansException {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
loadMap = applicationContext.getBeansOfType(ApplicationService.class);
getApplicationServiceList();
}
/**
@ -101,4 +102,12 @@ public class ApplicationServiceLoader implements ApplicationContextAware {
return impl;
}
public void addApplicationService(List<String> beanNameList) {
Map<String, ApplicationService> applicationServiceMap = new HashMap<>(16);
for (String beanName : beanNameList) {
applicationServiceMap.put(beanName,
applicationContext.getBean(beanName, ApplicationService.class));
}
loadMap.putAll(applicationServiceMap);
}
}

View File

@ -17,8 +17,11 @@
*/
package cn.topiam.employee.application.form;
import cn.topiam.employee.application.ApplicationService;
import cn.topiam.employee.common.repository.app.AppCertRepository;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.IdGenerator;
import cn.topiam.employee.common.repository.app.AppAccountRepository;
import cn.topiam.employee.common.repository.app.AppFormConfigRepository;
import cn.topiam.employee.common.repository.app.AppRepository;
/**
@ -27,20 +30,40 @@ import cn.topiam.employee.common.repository.app.AppRepository;
* @author TopIAM
* Created by support@topiam.cn on 2022/8/23 20:58
*/
public abstract class AbstractFormApplicationService implements ApplicationService {
public abstract class AbstractFormApplicationService implements FormApplicationService {
@Override
public void delete(String appId) {
//删除应用
appRepository.deleteById(Long.valueOf(appId));
//删除应用账户
appAccountRepository.deleteAllByAppId(Long.valueOf(appId));
// 删除应用配置
appFormConfigRepository.deleteByAppId(Long.valueOf(appId));
}
/**
* AppCertRepository
*/
protected final AppCertRepository appCertRepository;
/**
* ApplicationRepository
*/
protected final AppRepository appRepository;
protected final AppRepository appRepository;
protected AbstractFormApplicationService(AppCertRepository appCertRepository,
AppRepository appRepository) {
this.appCertRepository = appCertRepository;
/**
* AppAccountRepository
*/
protected final AppAccountRepository appAccountRepository;
protected final AppFormConfigRepository appFormConfigRepository;
/**
* IdGenerator
*/
protected final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
protected AbstractFormApplicationService(AppRepository appRepository,
AppAccountRepository appAccountRepository,
AppFormConfigRepository appFormConfigRepository) {
this.appRepository = appRepository;
this.appAccountRepository = appAccountRepository;
this.appFormConfigRepository = appFormConfigRepository;
}
}

View File

@ -0,0 +1,48 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form;
import cn.topiam.employee.application.ApplicationService;
import cn.topiam.employee.application.form.model.FormProtocolConfig;
import cn.topiam.employee.common.entity.app.AppAccountEntity;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/20 23:20
*/
public interface FormApplicationService extends ApplicationService {
/**
*
*
* @param appCode {@link String}
* @return {@link FormProtocolConfig}
*/
FormProtocolConfig getProtocolConfig(String appCode);
/**
*
*
* @param appId {@link Long}
* @param userId {@link Long}
* @return {@link FormProtocolConfig}
*/
AppAccountEntity getAppAccount(Long appId, Long userId);
}

View File

@ -17,15 +17,45 @@
*/
package cn.topiam.employee.application.form;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.validation.ConstraintViolationException;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.stereotype.Component;
import cn.topiam.employee.common.enums.app.AppProtocol;
import cn.topiam.employee.common.enums.app.AppType;
import cn.topiam.employee.common.repository.app.AppCertRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.application.exception.AppNotExistException;
import cn.topiam.employee.application.form.converter.AppFormConfigConverter;
import cn.topiam.employee.application.form.model.FormProtocolConfig;
import cn.topiam.employee.application.form.pojo.AppFormSaveConfigParam;
import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.common.entity.app.AppAccountEntity;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.AppFormConfigEntity;
import cn.topiam.employee.common.entity.app.po.AppFormConfigPO;
import cn.topiam.employee.common.enums.app.*;
import cn.topiam.employee.common.exception.app.AppAccountNotExistException;
import cn.topiam.employee.common.repository.app.AppAccountRepository;
import cn.topiam.employee.common.repository.app.AppFormConfigRepository;
import cn.topiam.employee.common.repository.app.AppRepository;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.util.BeanUtils;
import cn.topiam.employee.support.util.HttpUrlUtils;
import cn.topiam.employee.support.validation.ValidationHelp;
import lombok.extern.slf4j.Slf4j;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE;
import static cn.topiam.employee.common.constants.ProtocolConstants.FormEndpointConstants.IDP_FORM_SSO_INITIATOR;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME;
/**
* Form
@ -33,6 +63,7 @@ import cn.topiam.employee.common.repository.app.AppRepository;
* @author TopIAM
* Created by support@topiam.cn on 2022/8/20 23:20
*/
@Slf4j
@Component
public class FormStandardApplicationServiceImpl extends AbstractFormApplicationService {
@ -44,6 +75,51 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public void saveConfig(String appId, Map<String, Object> config) {
AppFormSaveConfigParam model;
try {
ObjectMapper mapper = new ObjectMapper();
String value = mapper.writeValueAsString(config);
// 指定序列化输入的类型
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
model = mapper.readValue(value, AppFormSaveConfigParam.class);
} catch (Exception e) {
throw new TopIamException(e.getMessage());
}
//@formatter:off
ValidationHelp.ValidationResult<AppFormSaveConfigParam> validationResult = ValidationHelp.validateEntity(model);
if (validationResult.isHasErrors()) {
throw new ConstraintViolationException(validationResult.getConstraintViolations());
}
//@formatter:on
//1、修改基本信息
Optional<AppEntity> optional = appRepository.findById(Long.valueOf(appId));
if (optional.isEmpty()) {
AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!");
log.error(AuditContext.getContent());
throw new AppNotExistException();
}
AppEntity appEntity = optional.get();
appEntity.setAuthorizationType(model.getAuthorizationType());
Map<String, String> variables = new HashMap<>(16);
variables.put(APP_CODE, appEntity.getCode());
StringSubstitutor sub = new StringSubstitutor(variables, "{", "}");
appEntity.setInitLoginUrl(sub.replace(HttpUrlUtils
.format(ServerContextHelp.getPortalPublicBaseUrl() + IDP_FORM_SSO_INITIATOR)));
appEntity.setInitLoginType(model.getInitLoginType());
appRepository.save(appEntity);
//2、修改 表单代填 配置
Optional<AppFormConfigEntity> form = appFormConfigRepository
.findByAppId(Long.valueOf(appId));
if (form.isEmpty()) {
AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!");
log.error(AuditContext.getContent());
throw new AppNotExistException();
}
AppFormConfigEntity entity = form.get();
AppFormConfigEntity formConfig = appFormConfigConverter
.appFormSaveConfigParamToEntity(model);
BeanUtils.merge(formConfig, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME);
appFormConfigRepository.save(entity);
}
/**
@ -54,7 +130,8 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public Object getConfig(String appId) {
return null;
AppFormConfigPO po = appFormConfigRepository.getByAppId(Long.valueOf(appId));
return appFormConfigConverter.entityConverterToFormConfigResult(po);
}
/**
@ -64,7 +141,7 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public String getCode() {
return "form";
return AppProtocol.FORM.getCode();
}
/**
@ -74,7 +151,7 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public String getName() {
return "表单代填";
return AppProtocol.FORM.getDesc();
}
/**
@ -124,7 +201,7 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public String getBase64Icon() {
return "data:image/svg+xml;base64,PHN2ZyB0PSIxNjYwOTc4MDk3OTkyIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMxNDMiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMjk0LjkxMiA0MTQuMjA4aDg2LjUyOHYzNDIuMDE2SDI5NC45MTJ6TTQyNC45NiA0MTQuMjA4aDMwMy4xMDR2MzQyLjAxNkg0MjQuOTZ6TTI5NC45MTIgMjY3LjI2NGg0MzMuMTUydjk3Ljc5MkgyOTQuOTEyeiIgZmlsbD0iIzI3QTNGRiIgcC1pZD0iMzE0NCI+PC9wYXRoPjxwYXRoIGQ9Ik05MjEuNiAwSDEwMi40YTEwMi40IDEwMi40IDAgMCAwLTEwMi40IDEwMi40djgxOS4yYTEwMi40IDEwMi40IDAgMCAwIDEwMi40IDEwMi40aDgxOS4yYTEwMi40IDEwMi40IDAgMCAwIDEwMi40LTEwMi40VjEwMi40YTEwMi40IDEwMi40IDAgMCAwLTEwMi40LTEwMi40eiBtLTE1MC4wMTYgNzgwLjhhMjMuMDQgMjMuMDQgMCAwIDEtMjEuNTA0IDI0LjA2NEgyNzMuNDA4YTIzLjA0IDIzLjA0IDAgMCAxLTIxLjUwNC0yNC4wNjRWMjQyLjY4OGEyMy4wNCAyMy4wNCAwIDAgMSAyMS41MDQtMjQuMDY0aDQ3Ni42NzJhMjMuMDQgMjMuMDQgMCAwIDEgMjEuNTA0IDI0LjA2NHoiIGZpbGw9IiMyN0EzRkYiIHAtaWQ9IjMxNDUiPjwvcGF0aD48L3N2Zz4=";
return "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjcyNDA3MTc3NTExIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ5MjkiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTQ2Ny4yNyA4NzAuMzNIMTgzLjczYy0yMC41OCAwLTM3LjMyLTE1LjE2LTM3LjMyLTMzLjhWMTg1Ljg2YzAtMTguNjMgMTYuNzQtMzMuNzkgMzcuMzItMzMuNzlINzE4YzIwLjU3IDAgMzcuMzEgMTUuMTYgMzcuMzEgMzMuNzl2MjYyLjU5YTI0IDI0IDAgMSAwIDQ4IDBWMTg1Ljg2YzAtNDUuMS0zOC4yNy04MS43OS04NS4zMS04MS43OUgxODMuNzNjLTQ3IDAtODUuMzIgMzYuNjktODUuMzIgODEuNzl2NjUwLjY3YzAgNDUuMSAzOC4yNyA4MS44IDg1LjMyIDgxLjhoMjgzLjU0YTI0IDI0IDAgMCAwIDAtNDh6IiBmaWxsPSIjNjc3Nzg3IiBwLWlkPSI0OTMwIj48L3BhdGg+PHBhdGggZD0iTTkyMi4zOSA2NzQuNThsLTAuMzQtMi4xOGE0Ni4zOCA0Ni4zOCAwIDAgMC0zOS41OC0zOC42bC0yLjE5LTAuMjlhMzcuMzcgMzcuMzcgMCAwIDEtMzIuNDEtMzdBMzYuNDQgMzYuNDQgMCAwIDEgODUwIDU4NS42bDAuNzYtMi4zMmE0Ni4xOSA0Ni4xOSAwIDAgMC0yMS40Ni01NC42N2wtNDEuNDMtMjNhNDYuMDggNDYuMDggMCAwIDAtNTMuNDQgNi4yNWwtMS41OCAxLjQzYTExMi4zNSAxMTIuMzUgMCAwIDEtMTAuNiA4LjcxYy04LjgxIDYuMTYtMTUuNDIgOC4zNC0xOC40IDguMzRzLTkuNzItMi4yMi0xOC40OC04LjQ1YTEwMS42MSAxMDEuNjEgMCAwIDEtMTAuMjYtOC40NGwtMS41Ni0xLjQ4YTQ2IDQ2IDAgMCAwLTU0LTdsLTQzLjE4IDIzLjY3YTQ2LjEgNDYuMSAwIDAgMC0yMS41OSA1NWwwLjc4IDIuMzRhMzUuNyAzNS43IDAgMCAxLTMwLjMzIDQ3LjYxbC0yLjE4IDAuMjlhNDYuMzggNDYuMzggMCAwIDAtMzkuNTcgMzguNTRsLTAuMzQgMi4xOGMtMS40OCA5LjM0LTMuMjMgMjMuMDUtMy4yMyAzNS4zMyAwIDEyLjA5IDEuNzUgMjUuODEgMy4yMiAzNS4ybDAuMzQgMi4xOGE0Ni4zOCA0Ni4zOCAwIDAgMCAzOS42MiAzOC41OWwyLjE5IDAuMjlhMzUuNzUgMzUuNzUgMCAwIDEgMzAuMjcgNDcuNjhsLTAuNzcgMi4zMUE0Ni4xNiA0Ni4xNiAwIDAgMCA1NzYuMDYgODkxbDQwLjI2IDIyLjUxYTQ1Ljg3IDQ1Ljg3IDAgMCAwIDU0LjQ1LTdsMS41OS0xLjUyYTEwMS4zNiAxMDEuMzYgMCAwIDEgMTAuNTEtOC44M2M5LTYuNiAxNS43NS04Ljk0IDE4LjgxLTguOTQgMS41NyAwIDcuNTYgMC42NiAxOS4wNyA5LjE4YTk3LjU3IDk3LjU3IDAgMCAxIDEwLjMzIDguODFsMS41NyAxLjU0YTQ2LjM4IDQ2LjM4IDAgMCAwIDU0LjU5IDcuNDlsNDItMjMuMTNhNDYuMTYgNDYuMTYgMCAwIDAgMjEuNTctNTQuNzNMODUwIDgzNGEzNi43OSAzNi43OSAwIDAgMS0yLjE0LTEwLjkzIDM3LjMyIDM3LjMyIDAgMCAxIDMyLjM2LTM3bDIuMi0wLjI5QTQ2LjM3IDQ2LjM3IDAgMCAwIDkyMiA3NDcuMjRsMC4zNS0yLjE3YzEuMTktNy42MiAzLjItMjIuMzMgMy4yLTM1LjIyIDAuMDQtMTIuMjUtMS43LTI1Ljk0LTMuMTYtMzUuMjd6IG0tNDcuNDIgNjNsLTAuMTMgMC43OS0wLjc5IDAuMWE4NS40NSA4NS40NSAwIDAgMC03NC4xOCA4NC41OCA4My43OSA4My43OSAwIDAgMCA0LjUyIDI1Ljg1bDAuMjggMC44NS0zOS4zOCAyMS43LTAuNTctMC41NmExNDcuNiAxNDcuNiAwIDAgMC0xNS40NC0xMy4xOGMtMTYuNjUtMTIuMzItMzIuNjctMTguNTctNDcuNi0xOC41N3MtMzAuNzEgNi4xMy00Ny4xNyAxOC4yMWExNTMuNiAxNTMuNiAwIDAgMC0xNS40MSAxMi45NGwtMC41NyAwLjU2LTM3LjY5LTIxLjA3IDAuMjgtMC44NGE4NC4xNSA4NC4xNSAwIDAgMCA0LjU1LTI1LjkgODUuNDIgODUuNDIgMCAwIDAtNzQuMTYtODQuNTdsLTAuOC0wLjExLTAuMTItMC43OWExOTMuMzMgMTkzLjMzIDAgMCAxLTIuNTktMjcuNzIgMTkxLjkgMTkxLjkgMCAwIDEgMi41OS0yNy44NWwwLjEzLTAuNzkgMC43OS0wLjExYTg1LjUgODUuNSAwIDAgMCA3NC4xNi04NC41NyA4My40NSA4My40NSAwIDAgMC00LjUyLTI1Ljc3bC0wLjI5LTAuODYgNDAuNi0yMi4yNSAwLjU3IDAuNTRhMTQ2LjE5IDE0Ni4xOSAwIDAgMCAxNS40NSAxMi43MmMxNi4yMiAxMS41NCAzMS44MiAxNy4zOSA0Ni4zNyAxNy4zOSAxNC4zNCAwIDI5LjgyLTUuNzQgNDYtMTcuMDdsMC4xMy0wLjA5YTE1Ny40MSAxNTcuNDEgMCAwIDAgMTUuMjMtMTIuMzZsMC41OC0wLjUyIDM4Ljg5IDIxLjU3LTAuMjggMC44NGE4My45IDgzLjkgMCAwIDAtNC41MiAyNS44NiA4NS41MSA4NS41MSAwIDAgMCA3NC4xOCA4NC41OGwwLjc5IDAuMSAwLjEzIDAuNzlhMTk2LjUyIDE5Ni41MiAwIDAgMSAyLjYyIDI3Ljg2IDE5My43MSAxOTMuNzEgMCAwIDEtMi42IDI3Ljc2eiIgZmlsbD0iIzY3Nzc4NyIgcC1pZD0iNDkzMSI+PC9wYXRoPjxwYXRoIGQ9Ik03MDIuNzcgNjMzLjQ1YTc2LjEzIDc2LjEzIDAgMSAwIDc2LjEzIDc2LjEzIDc2LjIxIDc2LjIxIDAgMCAwLTc2LjEzLTc2LjEzeiBtMCAxMDQuMjZhMjguMTMgMjguMTMgMCAxIDEgMjguMTMtMjguMTMgMjguMTYgMjguMTYgMCAwIDEtMjguMTMgMjguMTN6TTU1OS4yMyA0ODguNDVhMjQgMjQgMCAwIDAtMjQtMjRIMjM3Ljc0YTI0IDI0IDAgMSAwIDAgNDhoMjk3LjQ5YTI0IDI0IDAgMCAwIDI0LTI0ek02ODIuMzMgMzA3LjIxYTI0IDI0IDAgMCAwLTI0LTI0SDIzNy43NGEyNCAyNCAwIDAgMCAwIDQ4aDQyMC41OWEyNCAyNCAwIDAgMCAyNC0yNHpNMjQzLjIxIDYzOC45NGEyNCAyNCAwIDAgMCAwIDQ4aDE4My45NGEyNCAyNCAwIDEgMCAwLTQ4eiIgZmlsbD0iIzY3Nzc4NyIgcC1pZD0iNDkzMiI+PC9wYXRoPjwvc3ZnPg==";
}
/**
@ -135,22 +212,50 @@ public class FormStandardApplicationServiceImpl extends AbstractFormApplicationS
*/
@Override
public String create(String name, String remark) {
return "";
//1、创建应用
AppEntity appEntity = new AppEntity();
appEntity.setName(name);
appEntity.setCode(
org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(32).toLowerCase());
appEntity.setTemplate(getCode());
appEntity.setType(getType());
appEntity.setEnabled(true);
appEntity.setProtocol(getProtocol());
appEntity.setClientId(idGenerator.generateId().toString().replace("-", ""));
appEntity.setClientSecret(idGenerator.generateId().toString().replace("-", ""));
appEntity.setInitLoginType(InitLoginType.PORTAL_OR_APP);
appEntity.setAuthorizationType(AuthorizationType.AUTHORIZATION);
appEntity.setRemark(remark);
appRepository.save(appEntity);
AppFormConfigEntity appFormConfig = new AppFormConfigEntity();
appFormConfig.setAppId(appEntity.getId());
//提交类型
appFormConfig.setSubmitType(FormSubmitType.POST);
appFormConfigRepository.save(appFormConfig);
return String.valueOf(appEntity.getId());
}
/**
*
*
* @param appId {@link String} ID
*/
@Override
public void delete(String appId) {
public FormProtocolConfig getProtocolConfig(String appCode) {
AppFormConfigPO configPo = appFormConfigRepository.findByAppCode(appCode);
return appFormConfigConverter.appFormEntityToConfig(configPo);
}
protected FormStandardApplicationServiceImpl(AppCertRepository appCertRepository,
AppRepository appRepository) {
super(appCertRepository, appRepository);
@Override
public AppAccountEntity getAppAccount(Long appId, Long userId) {
return appAccountRepository.findByAppIdAndUserId(appId, userId)
.orElseThrow(AppAccountNotExistException::new);
}
private final AppFormConfigConverter appFormConfigConverter;
protected FormStandardApplicationServiceImpl(AppAccountRepository appAccountRepository,
AppFormConfigRepository appFormConfigRepository,
AppRepository appRepository,
AppFormConfigConverter appFormConfigConverter) {
super(appRepository, appAccountRepository, appFormConfigRepository);
this.appFormConfigConverter = appFormConfigConverter;
}
}

View File

@ -0,0 +1,117 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form.converter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.text.StringSubstitutor;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import cn.topiam.employee.application.form.model.FormProtocolConfig;
import cn.topiam.employee.application.form.pojo.AppFormConfigGetResult;
import cn.topiam.employee.application.form.pojo.AppFormProtocolEndpoint;
import cn.topiam.employee.application.form.pojo.AppFormSaveConfigParam;
import cn.topiam.employee.common.entity.app.AppFormConfigEntity;
import cn.topiam.employee.common.entity.app.po.AppFormConfigPO;
import cn.topiam.employee.core.context.ServerContextHelp;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE;
import static cn.topiam.employee.common.constants.ProtocolConstants.FormEndpointConstants.FORM_SSO_PATH;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2020/8/14 22:45
*/
@Mapper(componentModel = "spring")
public interface AppFormConfigConverter {
/**
* save entity
*
* @param config {@link AppFormSaveConfigParam}
* @return {@link AppFormConfigEntity}
*/
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "updateBy", ignore = true)
@Mapping(target = "remark", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "createBy", ignore = true)
@Mapping(target = "appId", ignore = true)
AppFormConfigEntity appFormSaveConfigParamToEntity(AppFormSaveConfigParam config);
/**
* entityconfig
*
* @param po {@link AppFormConfigPO}
* @return {@link FormProtocolConfig}
*/
FormProtocolConfig appFormEntityToConfig(AppFormConfigPO po);
/**
* po result
*
* @param po {@link AppFormConfigPO}
* @return {@link AppFormConfigGetResult}
*/
default AppFormConfigGetResult entityConverterToFormConfigResult(AppFormConfigPO po) {
if (po == null) {
return null;
}
AppFormConfigGetResult result = new AppFormConfigGetResult();
if (po.getAppId() != null) {
result.setAppId(String.valueOf(po.getAppId()));
}
result.setInitLoginType(po.getInitLoginType());
result.setInitLoginUrl(po.getInitLoginUrl());
result.setAuthorizationType(po.getAuthorizationType());
result.setLoginUrl(po.getLoginUrl());
result.setUsernameField(po.getUsernameField());
result.setPasswordField(po.getPasswordField());
result.setSubmitType(po.getSubmitType());
List<AppFormConfigEntity.OtherField> list = po.getOtherField();
if (list != null) {
result.setOtherField(new ArrayList<>(list));
}
result.setProtocolEndpoint(getProtocolEndpointDomain(po.getAppCode()));
return result;
}
/**
*
*
* @param appCode {@link String}
* @return {@link AppFormProtocolEndpoint}
*/
private AppFormProtocolEndpoint getProtocolEndpointDomain(String appCode) {
//@formatter:off
AppFormProtocolEndpoint domain = new AppFormProtocolEndpoint();
Map<String,String> variables = new HashMap<>(16);
variables.put(APP_CODE,appCode);
StringSubstitutor sub = new StringSubstitutor(variables, "{", "}");
//IDP SSO 端点
domain.setIdpSsoEndpoint(sub.replace(ServerContextHelp.getPortalPublicBaseUrl()+FORM_SSO_PATH));
return domain;
//@formatter:on
}
}

View File

@ -0,0 +1,77 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form.model;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import cn.topiam.employee.common.entity.app.AppFormConfigEntity;
import cn.topiam.employee.common.enums.app.FormSubmitType;
import lombok.Builder;
import lombok.Data;
/**
* Form
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/28 21:43
*/
@Data
@Builder
public class FormProtocolConfig implements Serializable {
@Serial
private static final long serialVersionUID = -3671812647788723766L;
/**
* APP ID
*/
private String appId;
/**
* APP Code
*/
private String appCode;
/**
* URL
*/
private String loginUrl;
/**
*
*/
private String usernameField;
/**
*
*/
private String passwordField;
/**
*
*/
private FormSubmitType submitType;
/**
*
*/
private List<AppFormConfigEntity.OtherField> otherField;
}

View File

@ -0,0 +1,101 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form.pojo;
import java.io.Serializable;
import java.util.List;
import cn.topiam.employee.common.entity.app.AppFormConfigEntity;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.FormSubmitType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Form
*
* @author TopIAM
* Created by support@topiam.cn on 2022/5/31 22:46
*/
@Data
@Schema(description = "Form 配置返回结果")
public class AppFormConfigGetResult implements Serializable {
/**
* id
*/
@Schema(description = "应用id")
private String appId;
/**
* SSO
*/
@Parameter(description = "SSO 发起方")
private InitLoginType initLoginType;
/**
* SSO
*/
@Parameter(description = "SSO 登录链接")
private String initLoginUrl;
/**
*
*/
@Parameter(description = "SSO 授权范围")
private AuthorizationType authorizationType;
/**
* URL
*/
@Schema(description = "登录URL")
private String loginUrl;
/**
*
*/
@Schema(description = "登录名属性名称")
private String usernameField;
/**
*
*/
@Schema(description = "登录密码属性名称")
private String passwordField;
/**
*
*/
@Schema(description = "登录提交方式")
private FormSubmitType submitType;
/**
*
*/
@Schema(description = "登录其他信息")
private List<AppFormConfigEntity.OtherField> otherField;
/**
*
*/
@Schema(description = "协议端点")
private AppFormProtocolEndpoint protocolEndpoint;
}

View File

@ -0,0 +1,46 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form.pojo;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/6/4 23:37
*/
@Data
@Schema(description = "协议端点")
public class AppFormProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = -2261602995152894964L;
/**
* IDP SSO
*/
@Parameter(description = "IDP SSO 端点")
private String idpSsoEndpoint;
}

View File

@ -0,0 +1,93 @@
/*
* eiam-application-form - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.form.pojo;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import javax.validation.constraints.NotNull;
import cn.topiam.employee.common.entity.app.AppFormConfigEntity;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.FormSubmitType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/13 22:45
*/
@Data
@Schema(description = "保存 表单代填 应用配置参数")
public class AppFormSaveConfigParam implements Serializable {
@Serial
private static final long serialVersionUID = 7257798528680745281L;
/**
* SSO
*/
@NotNull(message = "SSO范围不能为空")
@Schema(description = "SSO范围")
private AuthorizationType authorizationType;
/**
* SSO
*/
@NotNull(message = "SSO发起方不能为空")
@Schema(description = "SSO发起方")
private InitLoginType initLoginType;
/**
* URL
*/
@NotNull(message = "登录URL不能为空")
@Schema(description = "登录URL")
private String loginUrl;
/**
*
*/
@NotNull(message = "登录名属性名称不能为空")
@Schema(description = "登录名属性名称")
private String usernameField;
/**
*
*/
@NotNull(message = "登录密码属性名称不能为空")
@Schema(description = "登录密码属性名称")
private String passwordField;
/**
*
*/
@NotNull(message = "登录提交方式不能为空")
@Schema(description = "登录提交方式")
private FormSubmitType submitType;
/**
*
*/
@Schema(description = "登录其他信息")
private List<AppFormConfigEntity.OtherField> otherField;
}

View File

@ -40,7 +40,7 @@ public abstract class AbstractOidcApplicationService extends AbstractApplication
appAccountRepository.deleteAllByAppId(Long.valueOf(appId));
//删除应用权限策略
appAccessPolicyRepository.deleteAllByAppId(Long.valueOf(appId));
//删除SAML2配置
//删除OIDC配置
appOidcConfigRepository.deleteByAppId(Long.valueOf(appId));
}

View File

@ -39,7 +39,7 @@ import com.google.common.collect.Sets;
import cn.topiam.employee.application.exception.AppNotExistException;
import cn.topiam.employee.application.oidc.converter.AppOidcStandardConfigConverter;
import cn.topiam.employee.application.oidc.model.AppOidcStandardSaveConfigParam;
import cn.topiam.employee.application.oidc.pojo.AppOidcStandardSaveConfigParam;
import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.AppOidcConfigEntity;

View File

@ -25,8 +25,9 @@ import org.apache.commons.text.StringSubstitutor;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import cn.topiam.employee.application.oidc.model.AppOidcStandardConfigGetResult;
import cn.topiam.employee.application.oidc.model.AppOidcStandardSaveConfigParam;
import cn.topiam.employee.application.oidc.pojo.AppOidcProtocolEndpoint;
import cn.topiam.employee.application.oidc.pojo.AppOidcStandardConfigGetResult;
import cn.topiam.employee.application.oidc.pojo.AppOidcStandardSaveConfigParam;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.common.entity.app.AppOidcConfigEntity;
import cn.topiam.employee.common.entity.app.po.AppOidcConfigPO;
@ -88,6 +89,7 @@ public interface AppOidcStandardConfigConverter {
* @param config {@link AppOidcConfigEntity}
* @return {@link AppOidcConfigEntity}
*/
@Mapping(target = "responseTypes", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "updateBy", ignore = true)
@Mapping(target = "remark", ignore = true)
@ -101,11 +103,11 @@ public interface AppOidcStandardConfigConverter {
*
*
* @param appCode {@link String}
* @return {@link AppOidcStandardConfigGetResult.ProtocolEndpoint}
* @return {@link AppOidcProtocolEndpoint}
*/
private AppOidcStandardConfigGetResult.ProtocolEndpoint getProtocolEndpointDomain(String appCode) {
private AppOidcProtocolEndpoint getProtocolEndpointDomain(String appCode) {
//@formatter:off
AppOidcStandardConfigGetResult.ProtocolEndpoint domain = new AppOidcStandardConfigGetResult.ProtocolEndpoint();
AppOidcProtocolEndpoint domain = new AppOidcProtocolEndpoint();
//issues
Map<String,String> variables = new HashMap<>(16);
variables.put(APP_CODE,appCode);

View File

@ -0,0 +1,81 @@
/*
* eiam-application-oidc - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.oidc.pojo;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/6/4 23:37
*/
@Data
@Schema(description = "协议端点")
public class AppOidcProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = -2261602995152894964L;
/**
* oidcIssuer
*/
@Parameter(description = "Issuer")
private String issuer;
/**
* discoveryEndpoint
*/
@Parameter(description = "Discovery Endpoint")
private String discoveryEndpoint;
/**
* UserinfoEndpoint
*/
@Parameter(description = "UserInfo Endpoint")
private String userinfoEndpoint;
/**
* jwksEndpoint
*/
@Parameter(description = "Jwks Endpoint")
private String jwksEndpoint;
/**
* revokeEndpoint
*/
@Parameter(description = "Revoke Endpoint")
private String revokeEndpoint;
/**
* tokenEndpoint
*/
@Parameter(description = "Token Endpoint")
private String tokenEndpoint;
/**
* authorizationEndpoint
*/
@Parameter(description = "Authorization Endpoint")
private String authorizationEndpoint;
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.oidc.model;
package cn.topiam.employee.application.oidc.pojo;
import java.io.Serial;
import java.io.Serializable;
@ -40,163 +40,109 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class AppOidcStandardConfigGetResult implements Serializable {
@Serial
private static final long serialVersionUID = 4177874005424703372L;
private static final long serialVersionUID = 4177874005424703372L;
/**
* APP ID
*/
@Parameter(description = "appId")
private Long appId;
private Long appId;
/**
* SSO
*/
@Parameter(description = "SSO 发起方")
private InitLoginType initLoginType;
private InitLoginType initLoginType;
/**
* SSO
*/
@Parameter(description = "SSO 登录链接")
private String initLoginUrl;
private String initLoginUrl;
/**
*
*/
@Parameter(description = "SSO 授权范围")
private AuthorizationType authorizationType;
private AuthorizationType authorizationType;
/**
* authorizationGrantTypes
*/
@Parameter(description = "认证授权类型")
private Set<String> authGrantTypes;
private Set<String> authGrantTypes;
/**
*
*/
@Parameter(description = "客户端认证方式")
private Set<String> clientAuthMethods;
private Set<String> clientAuthMethods;
/**
* URI
*/
@Parameter(description = "重定向URI")
private Set<String> redirectUris;
private Set<String> redirectUris;
/**
* scopes
*/
@Parameter(description = "授权范围")
private Set<String> grantScopes;
private Set<String> grantScopes;
/**
* PKCE
*/
@Parameter(description = "启用PKCE")
private Boolean requireProofKey;
private Boolean requireProofKey;
/**
* Endpoint
*/
@Parameter(description = "令牌 Endpoint 身份验证签名算法")
private String tokenEndpointAuthSigningAlgorithm;
private String tokenEndpointAuthSigningAlgorithm;
/**
*
*/
@Parameter(description = "是否需要授权同意")
private Boolean requireAuthConsent;
private Boolean requireAuthConsent;
/**
* 访
*/
@Parameter(description = "访问令牌有效时间")
private String accessTokenTimeToLive;
private String accessTokenTimeToLive;
/**
*
*/
@Parameter(description = "刷新令牌有效时间")
private String refreshTokenTimeToLive;
private String refreshTokenTimeToLive;
/**
* ID token
*/
@Parameter(description = "ID 令牌有效时间")
private String idTokenTimeToLive;
private String idTokenTimeToLive;
/**
* id
*/
@Parameter(description = "Id令牌签名算法")
private String idTokenSignatureAlgorithm;
private String idTokenSignatureAlgorithm;
/**
*
*/
@Parameter(description = "协议端点域")
private ProtocolEndpoint protocolEndpoint;
private AppOidcProtocolEndpoint protocolEndpoint;
/**
* Access Token
*/
@Parameter(description = "Access Token 格式")
private String accessTokenFormat;
private String accessTokenFormat;
/**
*
*/
@Parameter(description = "是否重用刷新令牌")
private Boolean reuseRefreshToken;
private Boolean reuseRefreshToken;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/6/4 23:37
*/
@Data
@Schema(description = "协议端点")
public static class ProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = -2261602995152894964L;
/**
* oidcIssuer
*/
@Parameter(description = "Issuer")
private String issuer;
/**
* discoveryEndpoint
*/
@Parameter(description = "Discovery Endpoint")
private String discoveryEndpoint;
/**
* UserinfoEndpoint
*/
@Parameter(description = "UserInfo Endpoint")
private String userinfoEndpoint;
/**
* jwksEndpoint
*/
@Parameter(description = "Jwks Endpoint")
private String jwksEndpoint;
/**
* revokeEndpoint
*/
@Parameter(description = "Revoke Endpoint")
private String revokeEndpoint;
/**
* tokenEndpoint
*/
@Parameter(description = "Token Endpoint")
private String tokenEndpoint;
/**
* authorizationEndpoint
*/
@Parameter(description = "Authorization Endpoint")
private String authorizationEndpoint;
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.oidc.model;
package cn.topiam.employee.application.oidc.pojo;
import java.io.Serial;
import java.io.Serializable;

View File

@ -1,5 +1,5 @@
/*
* eiam-application-saml2 - Employee Identity and Access Management Program
* eiam-application-oidc - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application;
package cn.topiam.employee.application.oidc.pojo;

View File

@ -26,11 +26,14 @@ import org.mapstruct.Mapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AlternativeJdkIdGenerator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.IdGenerator;
import cn.topiam.employee.application.AbstractApplicationService;
import cn.topiam.employee.application.Saml2ApplicationService;
import cn.topiam.employee.application.exception.AppCertNotExistException;
import cn.topiam.employee.application.saml2.model.Saml2ProtocolConfig;
import cn.topiam.employee.application.saml2.model.Saml2SsoModel;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.app.AppAccountEntity;
import cn.topiam.employee.common.entity.app.AppCertEntity;
@ -43,8 +46,6 @@ import cn.topiam.employee.common.exception.app.AppAccountNotExistException;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.app.*;
import cn.topiam.employee.common.util.SamlKeyStoreProvider;
import cn.topiam.employee.core.protocol.Saml2ProtocolConfig;
import cn.topiam.employee.core.protocol.Saml2SsoModel;
import cn.topiam.employee.core.security.util.SecurityUtils;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import static cn.topiam.employee.common.enums.app.SamlNameIdValueType.*;
@ -134,6 +135,11 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService
*/
protected final AppSaml2ConfigRepository appSaml2ConfigRepository;
/**
* IdGenerator
*/
protected final IdGenerator idGenerator;
protected AbstractSamlAppService(AppCertRepository appCertRepository,
AppAccountRepository appAccountRepository,
AppAccessPolicyRepository appAccessPolicyRepository,
@ -141,6 +147,7 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService
AppSaml2ConfigRepository appSaml2ConfigRepository) {
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository);
this.appSaml2ConfigRepository = appSaml2ConfigRepository;
this.idGenerator = new AlternativeJdkIdGenerator();
}
@Mapper(componentModel = "spring")

View File

@ -1,5 +1,5 @@
/*
* eiam-application-core - Employee Identity and Access Management Program
* eiam-application-saml2 - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,10 +15,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application;
package cn.topiam.employee.application.saml2;
import cn.topiam.employee.core.protocol.Saml2ProtocolConfig;
import cn.topiam.employee.core.protocol.Saml2SsoModel;
import cn.topiam.employee.application.ApplicationService;
import cn.topiam.employee.application.saml2.model.Saml2ProtocolConfig;
import cn.topiam.employee.application.saml2.model.Saml2SsoModel;
/**
*

View File

@ -28,9 +28,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.application.exception.AppNotExistException;
import cn.topiam.employee.application.saml2.converter.AppSaml2StandardConfigConverter;
import cn.topiam.employee.application.saml2.model.AppSaml2StandardSaveConfigParam;
import cn.topiam.employee.application.saml2.pojo.AppSaml2StandardSaveConfigParam;
import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity;
@ -69,6 +71,7 @@ public class Saml2StandardApplicationServiceImpl extends AbstractSamlAppService
public void saveConfig(String appId, Map<String, Object> config) {
AppSaml2StandardSaveConfigParam model;
try {
ObjectMapper mapper = new ObjectMapper();
String value = mapper.writeValueAsString(config);
// 指定序列化输入的类型
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

View File

@ -1,5 +1,5 @@
/*
* eiam-application-core - Employee Identity and Access Management Program
* eiam-application-saml2 - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application;
package cn.topiam.employee.application.saml2;
import com.fasterxml.jackson.annotation.JsonValue;

View File

@ -22,9 +22,9 @@ import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import cn.topiam.employee.application.saml2.model.AppSaml2StandardConfigGetResult;
import cn.topiam.employee.application.saml2.model.AppSaml2StandardSaveConfigParam;
import cn.topiam.employee.application.saml2.model.Saml2ConverterUtils;
import cn.topiam.employee.application.saml2.pojo.AppSaml2StandardConfigGetResult;
import cn.topiam.employee.application.saml2.pojo.AppSaml2StandardSaveConfigParam;
import cn.topiam.employee.application.saml2.pojo.Saml2ConverterUtils;
import cn.topiam.employee.common.entity.app.AppSaml2ConfigEntity;
import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-application-saml2 - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.protocol;
package cn.topiam.employee.application.saml2.model;
import java.io.Serial;
import java.io.Serializable;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-application-saml2 - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.protocol;
package cn.topiam.employee.application.saml2.model;
import java.io.Serial;
import java.io.Serializable;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.saml2.model;
package cn.topiam.employee.application.saml2.pojo;
import java.io.Serial;
import java.io.Serializable;
@ -33,7 +33,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
*/
@Data
@Schema(description = "协议端点")
public class Saml2ProtocolEndpoint implements Serializable {
public class AppSaml2ProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = -2261602995152894964L;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.saml2.model;
package cn.topiam.employee.application.saml2.pojo;
import java.util.List;
import java.util.Map;
@ -160,7 +160,7 @@ public class AppSaml2StandardConfigGetResult {
*
*/
@Parameter(description = "协议端点域")
private Saml2ProtocolEndpoint protocolEndpoint;
private AppSaml2ProtocolEndpoint protocolEndpoint;
/**
*

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.saml2.model;
package cn.topiam.employee.application.saml2.pojo;
import java.io.Serial;
import java.io.Serializable;

View File

@ -15,10 +15,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.application.saml2.model;
package cn.topiam.employee.application.saml2.pojo;
import cn.topiam.employee.core.context.ServerContextHelp;
import static cn.topiam.employee.common.constants.ProtocolConstants.*;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
import static cn.topiam.employee.common.constants.ProtocolConstants.Saml2EndpointConstants;
/**
* Saml2ConverterUtils
@ -31,10 +32,10 @@ public class Saml2ConverterUtils {
* ID
*
* @param appCode {@link String}
* @return {@link Saml2ProtocolEndpoint}
* @return {@link AppSaml2ProtocolEndpoint}
*/
public static Saml2ProtocolEndpoint getProtocolEndpointDomain(String appCode) {
Saml2ProtocolEndpoint domain = new Saml2ProtocolEndpoint();
public static AppSaml2ProtocolEndpoint getProtocolEndpointDomain(String appCode) {
AppSaml2ProtocolEndpoint domain = new AppSaml2ProtocolEndpoint();
//IDP
String baseUrl = ServerContextHelp.getPortalPublicBaseUrl();
//元数据端点

View File

@ -1,5 +1,5 @@
/*
* eiam-common - Employee Identity and Access Management Program
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,12 +15,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.enums;
package cn.topiam.employee.authentication.common;
import java.util.List;
import com.google.common.collect.Lists;
import cn.topiam.employee.common.enums.AuthenticationType;
import cn.topiam.employee.common.enums.BaseEnum;
import cn.topiam.employee.support.web.converter.EnumConvert;
/**
@ -35,20 +37,17 @@ public enum IdentityProviderCategory implements BaseEnum {
*/
social("social", "社交", Lists.newArrayList(
IdentityProviderType.QQ,
IdentityProviderType.WECHAT_SCAN_CODE,
IdentityProviderType.WEIBO,
IdentityProviderType.GITHUB,
IdentityProviderType.GOOGLE,
IdentityProviderType.ALIPAY)),
IdentityProviderType.WECHAT_QR)),
/**
*
*/
enterprise("enterprise", "企业", Lists
.newArrayList(
IdentityProviderType.WECHATWORK_SCAN_CODE,
IdentityProviderType.DINGTALK_SCAN_CODE,
IdentityProviderType.WECHAT_WORK_QR,
IdentityProviderType.DINGTALK_QR,
IdentityProviderType.DINGTALK_OAUTH,
IdentityProviderType.LDAP));
IdentityProviderType.LDAP,
IdentityProviderType.FEISHU_OAUTH));
private final String code;

View File

@ -1,5 +1,5 @@
/*
* eiam-common - Employee Identity and Access Management Program
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,15 +15,13 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.enums.converter;
package cn.topiam.employee.authentication.common;
import java.util.Objects;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import cn.topiam.employee.common.enums.IdentityProviderCategory;
/**
* @author TopIAM
* Created by support@topiam.cn on 2020/12/11 19:42

View File

@ -23,7 +23,6 @@ import java.util.Map;
import org.springframework.transaction.annotation.Transactional;
import cn.topiam.employee.authentication.common.config.IdentityProviderConfig;
import cn.topiam.employee.common.enums.IdentityProviderType;
/**
* IdentityProviderService

View File

@ -0,0 +1,109 @@
/*
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.common;
import org.springframework.util.Assert;
import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZATION_REQUEST_URI;
import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH;
/**
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2022/12/31 15:18
*/
public record IdentityProviderType(String value,String name,String desc){
/**
*
*/
public static final IdentityProviderType FEISHU_OAUTH=new IdentityProviderType("feishu_oauth","飞书认证","通过飞书进行身份验证");
/**
*
*/
public static final IdentityProviderType DINGTALK_OAUTH=new IdentityProviderType("dingtalk_oauth","钉钉Oauth认证","通过钉钉进行身份认证");
/**
*
*/
public static final IdentityProviderType DINGTALK_QR=new IdentityProviderType("dingtalk_qr","钉钉扫码认证","通过钉钉扫码进行身份认证");
/**
*
*/
public static final IdentityProviderType WECHAT_QR=new IdentityProviderType("wechat_qr","微信扫码登录","通过微信扫码进行身份认证");
/**
*
*/
public static final IdentityProviderType WECHAT_WORK_QR=new IdentityProviderType("wechatwork_qr","企业微信扫码认证","通过企业微信同步的用户可使用企业微信扫码登录进行身份认证");
/**
* QQ
*/
public static final IdentityProviderType QQ=new IdentityProviderType("qq_oauth","QQ认证","通过QQ进行身份认证");
/**
* IDAP
*/
public static final IdentityProviderType LDAP=new IdentityProviderType("ldap","LDAP认证","通过 LDAP 进行身份验证");
/**
*
*/
public static final IdentityProviderType USERNAME_PASSWORD=new IdentityProviderType("username_password","用户名密码认证","通过用户名密码进行身份认证");
/**
*
*/
public static final IdentityProviderType SMS=new IdentityProviderType("sms","短信验证码认证","通过短信验证码进行身份认证");
/**
* Constructs an {@code IdentityProviderType} using the provided value.
*
* @param value the value of the authorization grant type
*/
public IdentityProviderType{Assert.hasText(value,"value cannot be empty");}
/**
* Returns the value of the authorization grant type.
*
* @return the value of the authorization grant type
*/
@Override public String value(){return this.value;}
@Override public boolean equals(Object obj){if(this==obj){return true;}if(obj==null||this.getClass()!=obj.getClass()){return false;}IdentityProviderType that=(IdentityProviderType)obj;return this.value().equals(that.value());}
@Override public int hashCode(){return this.value().hashCode();}
@Override
public String name() {
return name;
}
@Override
public String desc() {
return desc;
}
public String getLoginPathPrefix() {
return LOGIN_PATH + "/" + value();
}
public String getAuthorizationPathPrefix() {
return AUTHORIZATION_REQUEST_URI + "/" + value();
}
public static int size() {
return 9;
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-common - Employee Identity and Access Management Program
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,16 +15,18 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
package cn.topiam.employee.authentication.common.constant;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/12/30 01:06
* Created by support@topiam.cn on 2021/12/20 23:19
*/
public class CasUtils {
private static final Logger logger = LoggerFactory.getLogger(CasUtils.class);
public final class AuthenticationConstants {
}
/**
* ID
*/
public static final String PROVIDER_CODE = "providerId";
}

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,23 +15,20 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.security.captcha;
package cn.topiam.employee.authentication.common.exception;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.topiam.employee.support.exception.TopIamException;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/14 22:09
* Created by support@topiam.cn on 2022/12/20 22:50
*/
public interface CaptchaValidator {
/**
*
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @return {@link Boolean}
*/
boolean validate(HttpServletRequest request, HttpServletResponse response);
public class IdentityProviderNotExistException extends TopIamException {
public IdentityProviderNotExistException() {
super("idp_not_exist", "身份提供商不存在", BAD_REQUEST);
}
}

View File

@ -35,10 +35,11 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.authentication.common.IdentityProviderType;
import cn.topiam.employee.authentication.common.exception.IdentityProviderNotExistException;
import cn.topiam.employee.authentication.common.modal.IdpUser;
import cn.topiam.employee.authentication.common.service.UserIdpService;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.enums.IdentityProviderType;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.core.security.authentication.IdpAuthentication;
import cn.topiam.employee.core.security.userdetails.UserDetails;
@ -65,14 +66,18 @@ public abstract class AbstractIdpAuthenticationProcessingFilter extends
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @param provider {@link IdentityProviderType}
* @param providerId {@link String}
* @param providerCode {@link String}
* @param info {@link JSONObject}
* @return {@link Authentication}
*/
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response,
IdentityProviderType provider, String providerId,
IdentityProviderType provider, String providerCode,
IdpUser info) throws IOException {
IdentityProviderEntity identityProvider = identityProviderRepository
.findByCodeAndEnabledIsTrue(providerCode)
.orElseThrow(IdentityProviderNotExistException::new);
String providerId = String.valueOf(identityProvider.getId());
info.setProviderId(providerId);
info.setProviderType(provider);
//调用接口查询是否已绑定
@ -81,7 +86,7 @@ public abstract class AbstractIdpAuthenticationProcessingFilter extends
//是否自动绑定
if (!userIdpService.isAutoBindUserIdp(providerId)) {
setUserBindSessionContent(request, info);
return new IdpAuthentication(provider.getCode(), providerId);
return new IdpAuthentication(provider.value(), providerId);
}
//调用接口进行绑定操作
info.setProviderId(providerId);
@ -136,16 +141,16 @@ public abstract class AbstractIdpAuthenticationProcessingFilter extends
String providerId, HttpServletRequest request) {
//认证
UserDetails userDetails = userIdpService.getUserDetails(openId, providerId);
IdpAuthentication token = new IdpAuthentication(userDetails, provider.getCode(), providerId,
IdpAuthentication token = new IdpAuthentication(userDetails, provider.value(), providerId,
true, userDetails.getAuthorities());
// Allow subclasses to set the "details" property
token.setDetails(this.authenticationDetailsSource.buildDetails(request));
return token;
}
public IdentityProviderEntity getIdentityProviderEntity(String providerId) {
public IdentityProviderEntity getIdentityProviderEntity(String code) {
Optional<IdentityProviderEntity> optional = getIdentityProviderRepository()
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(code);
if (optional.isEmpty()) {
//无效身份提供商
OAuth2Error oauth2Error = new OAuth2Error(INVALID_IDP);

View File

@ -19,7 +19,7 @@ package cn.topiam.employee.authentication.common.modal;
import java.util.Map;
import cn.topiam.employee.common.enums.IdentityProviderType;
import cn.topiam.employee.authentication.common.IdentityProviderType;
import lombok.AllArgsConstructor;
import lombok.Builder;

View File

@ -0,0 +1,60 @@
/*
* eiam-authentication-core - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.common.util;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import cn.topiam.employee.core.security.authentication.IdpAuthentication;
import cn.topiam.employee.core.security.authentication.SmsAuthentication;
import cn.topiam.employee.core.security.mfa.MfaAuthentication;
import static cn.topiam.employee.authentication.common.IdentityProviderType.SMS;
import static cn.topiam.employee.authentication.common.IdentityProviderType.USERNAME_PASSWORD;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2022/12/31 14:29
*/
public class AuthenticationUtils {
/**
*
*
* @param authentication {@link Authentication}
* @return {@link String}
*/
public static String geAuthType(Authentication authentication) {
//用户名密码
if (authentication instanceof UsernamePasswordAuthenticationToken) {
return USERNAME_PASSWORD.value();
}
//身份提供商
if (authentication instanceof IdpAuthentication) {
return ((IdpAuthentication) authentication).getProviderType();
}
//短信登录
if (authentication instanceof SmsAuthentication) {
return SMS.value();
}
//MFA
if (authentication instanceof MfaAuthentication) {
return geAuthType(((MfaAuthentication) authentication).getFirst());
}
throw new IllegalArgumentException("未知认证对象");
}
}

View File

@ -51,9 +51,10 @@ import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.RESPONSE_TYPE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.DINGTALK_OAUTH;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.URL_AUTHORIZE;
import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkOauthAuthenticationFilter.getLoginUrl;
import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_OAUTH;
/**
*
@ -67,16 +68,11 @@ public class DingtalkOAuth2AuthorizationRequestRedirectFilter extends OncePerReq
private final Logger logger = LoggerFactory
.getLogger(DingtalkOAuth2AuthorizationRequestRedirectFilter.class);
/**
* ID
*/
public static final String PROVIDER_ID = "providerId";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher DINGTALK_OAUTH2_REQUEST_MATCHER = new AntPathRequestMatcher(
DINGTALK_OAUTH.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
DINGTALK_OAUTH.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
@ -108,9 +104,9 @@ public class DingtalkOAuth2AuthorizationRequestRedirectFilter extends OncePerReq
return;
}
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
throw new NullPointerException("未查询到身份提供商信息");
}
@ -121,7 +117,8 @@ public class DingtalkOAuth2AuthorizationRequestRedirectFilter extends OncePerReq
//构建授权请求
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode()
.clientId(config.getAppKey()).authorizationUri(URL_AUTHORIZE)
.redirectUri(getLoginUrl(providerId)).state(DEFAULT_STATE_GENERATOR.generateKey());
.redirectUri(getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey());
builder.parameters(parameters -> {
parameters.put(RESPONSE_TYPE, OAuth2ParameterNames.CODE);
parameters.put("prompt", "consent");

View File

@ -59,8 +59,9 @@ import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.support.util.HttpUrlUtils;
import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_OAUTH;
import static cn.topiam.employee.authentication.common.IdentityProviderType.DINGTALK_OAUTH;
import static cn.topiam.employee.authentication.common.IdentityProviderType.DINGTALK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
/**
*
@ -72,13 +73,14 @@ import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_OAUT
*/
@SuppressWarnings("DuplicatedCode")
public class DingtalkOauthAuthenticationFilter extends AbstractIdpAuthenticationProcessingFilter {
public static final String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_OAUTH
public final static String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_QR
.getLoginPathPrefix() + "/*";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
DINGTALK_OAUTH.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name());
DINGTALK_OAUTH.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
* Creates a new instance
@ -108,7 +110,7 @@ public class DingtalkOauthAuthenticationFilter extends AbstractIdpAuthentication
TraceUtils.put(UUID.randomUUID().toString());
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerId = variables.get(PROVIDER_CODE);
//code 钉钉新版登录为 authCode
String code = request.getParameter(AUTH_CODE);
if (StringUtils.isEmpty(code)) {
@ -154,7 +156,7 @@ public class DingtalkOauthAuthenticationFilter extends AbstractIdpAuthentication
}
//执行逻辑
IdpUser idpUser = IdpUser.builder().openId(user.getBody().getOpenId()).build();
return attemptAuthentication(request, response, DINGTALK_OAUTH, providerId, idpUser);
return attemptAuthentication(request, response, DINGTALK_QR, providerId, idpUser);
}
/**
@ -199,8 +201,8 @@ public class DingtalkOauthAuthenticationFilter extends AbstractIdpAuthentication
private Cache<String, String> cache;
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl()
+ DINGTALK_OAUTH.getLoginPathPrefix() + "/" + providerId;
String url = ServerContextHelp.getPortalPublicBaseUrl() + DINGTALK_QR.getLoginPathPrefix()
+ "/" + providerId;
return HttpUrlUtils.format(url);
}

View File

@ -59,17 +59,14 @@ import cn.topiam.employee.authentication.common.modal.IdpUser;
import cn.topiam.employee.authentication.common.service.UserIdpService;
import cn.topiam.employee.authentication.dingtalk.DingTalkIdpScanCodeConfig;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.enums.IdentityProviderType;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.support.util.HttpUrlUtils;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.GET_USERINFO_BY_CODE;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.GET_USERINFO_BY_USERID;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.GET_USERID_BY_UNIONID;
import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN_CODE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.DINGTALK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.*;
/**
*
@ -82,14 +79,13 @@ import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN
@SuppressWarnings("DuplicatedCode")
public class DingtalkScanCodeAuthenticationFilter extends
AbstractIdpAuthenticationProcessingFilter {
public static final String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_SCAN_CODE
public final static String DEFAULT_FILTER_PROCESSES_URI = DINGTALK_QR
.getLoginPathPrefix() + "/*";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
DINGTALK_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
HttpMethod.GET.name());
DINGTALK_QR.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", HttpMethod.GET.name());
/**
* Creates a new instance
@ -119,7 +115,7 @@ public class DingtalkScanCodeAuthenticationFilter extends
TraceUtils.put(UUID.randomUUID().toString());
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerId = variables.get(PROVIDER_CODE);
//code
String code = request.getParameter(OAuth2ParameterNames.CODE);
if (StringUtils.isEmpty(code)) {
@ -197,8 +193,7 @@ public class DingtalkScanCodeAuthenticationFilter extends
//4、执行逻辑
OapiV2UserGetResponse.UserGetResponse result = rspGetResponse.getResult();
IdpUser idpUser = IdpUser.builder().openId(result.getUserid()).build();
return attemptAuthentication(request, response, IdentityProviderType.DINGTALK_SCAN_CODE,
providerId, idpUser);
return attemptAuthentication(request, response, DINGTALK_QR, providerId, idpUser);
}
/**
@ -236,8 +231,8 @@ public class DingtalkScanCodeAuthenticationFilter extends
private Cache<String, String> cache;
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl()
+ DINGTALK_SCAN_CODE.getLoginPathPrefix() + "/" + providerId;
String url = ServerContextHelp.getPortalPublicBaseUrl() + DINGTALK_QR.getLoginPathPrefix()
+ "/" + providerId;
return HttpUrlUtils.format(url);
}

View File

@ -55,10 +55,11 @@ import cn.topiam.employee.support.util.HttpResponseUtils;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CODE;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.RESPONSE_TYPE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.DINGTALK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.APP_ID;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.SCAN_CODE_URL_AUTHORIZE;
import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthenticationFilter.getLoginUrl;
import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN_CODE;
/**
*
@ -72,16 +73,11 @@ public class DingtalkScanCodeAuthorizationRequestGetFilter extends OncePerReques
private final Logger logger = LoggerFactory
.getLogger(DingtalkScanCodeAuthorizationRequestGetFilter.class);
/**
* ID
*/
public static final String PROVIDER_ID = "providerId";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher DINGTALK_SCAN_CODE_REQUEST_MATCHER = new AntPathRequestMatcher(
DINGTALK_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
DINGTALK_QR.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
@ -109,9 +105,9 @@ public class DingtalkScanCodeAuthorizationRequestGetFilter extends OncePerReques
}
Map<String, String> variables = matcher.getVariables();
//校验身份提供商
String providerId = variables.get(PROVIDER_ID);
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
logger.error("身份提供商不存在");
throw new NullPointerException("身份提供商不存在");
@ -131,7 +127,7 @@ public class DingtalkScanCodeAuthorizationRequestGetFilter extends OncePerReques
.clientId(config.getAppKey())
.scopes(Sets.newHashSet("snsapi_login"))
.authorizationUri(SCAN_CODE_URL_AUTHORIZE)
.redirectUri(getLoginUrl(providerId))
.redirectUri(getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey())
.attributes(attributes);
builder.parameters(parameters -> {

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,37 +15,38 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.security.captcha.geetest;
package cn.topiam.employee.authentication.feishu;
import java.io.Serial;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotBlank;
import cn.topiam.employee.core.security.captcha.CaptchaProviderConfig;
import cn.topiam.employee.authentication.common.config.IdentityProviderConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/14 22:44
* Created by support@topiam.cn on 2022/12/19 22:58
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GeeTestCaptchaProviderConfig extends CaptchaProviderConfig {
public class FeiShuIdpScanCodeConfig extends IdentityProviderConfig {
@Serial
private static final long serialVersionUID = 3279601494863893521L;
/**
* ID
*/
@NotEmpty(message = "验证码ID不能为空")
private String captchaId;
private static final long serialVersionUID = -6850223527422243076L;
/**
* KEY
* APP ID
*/
@NotEmpty(message = "验证码KEY不能为空")
private String captchaKey;
@NotBlank(message = "APP ID 不能为空")
private String appId;
/**
* APP Secret
*/
@NotBlank(message = "APP Secret 不能为空")
private String appSecret;
}

View File

@ -0,0 +1,91 @@
/*
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.feishu.configurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import cn.topiam.employee.authentication.common.service.UserIdpService;
import cn.topiam.employee.authentication.feishu.filter.FeiShuAuthorizationRequestGetFilter;
import cn.topiam.employee.authentication.feishu.filter.FeiShuLoginAuthenticationFilter;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/19 23:58
*/
public final class FeiShuScanCodeAuthenticationConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, FeiShuScanCodeAuthenticationConfigurer<H>, FeiShuLoginAuthenticationFilter> {
private final IdentityProviderRepository identityProviderRepository;
private final UserIdpService userIdpService;
public FeiShuScanCodeAuthenticationConfigurer(IdentityProviderRepository identityProviderRepository,
UserIdpService userIdpService) {
Assert.notNull(identityProviderRepository, "identityProviderRepository must not be null");
Assert.notNull(userIdpService, "userIdpService must not be null");
this.identityProviderRepository = identityProviderRepository;
this.userIdpService = userIdpService;
}
/**
* Create the {@link RequestMatcher} given a loginProcessingUrl
*
* @param loginProcessingUrl creates the {@link RequestMatcher} based upon the
* loginProcessingUrl
* @return the {@link RequestMatcher} to use based upon the loginProcessingUrl
*/
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl);
}
@Override
public void init(H http) throws Exception {
//微信扫码登录认证
FeiShuLoginAuthenticationFilter loginAuthenticationFilter = new FeiShuLoginAuthenticationFilter(
identityProviderRepository, userIdpService);
this.setAuthenticationFilter(loginAuthenticationFilter);
//处理URL
super.loginProcessingUrl(FeiShuLoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
super.init(http);
}
@Override
public void configure(H http) throws Exception {
//微信扫码请求重定向
FeiShuAuthorizationRequestGetFilter requestRedirectFilter = new FeiShuAuthorizationRequestGetFilter(
identityProviderRepository);
http.addFilterBefore(requestRedirectFilter, OAuth2AuthorizationRequestRedirectFilter.class);
http.addFilterBefore(this.getAuthenticationFilter(), OAuth2LoginAuthenticationFilter.class);
super.configure(http);
}
public RequestMatcher getRequestMatcher() {
return new OrRequestMatcher(FeiShuAuthorizationRequestGetFilter.getRequestMatcher(),
FeiShuLoginAuthenticationFilter.getRequestMatcher());
}
}

View File

@ -0,0 +1,39 @@
/*
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.feishu.constant;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/19 23:19
*/
public final class FeiShuAuthenticationConstants {
public static final String AUTHORIZATION_REQUEST = "https://passport.feishu.cn/suite/passport/oauth/authorize";
public static final String ACCESS_TOKEN = "https://passport.feishu.cn/suite/passport/oauth/token";
public static final String USER_INFO = "https://passport.feishu.cn/suite/passport/oauth/userinfo";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
public static final String OPEN_ID = "open_id";
public static final String CODE = "code";
public static final String HREF = "href";
}

View File

@ -1,70 +0,0 @@
/*
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.feishu.filter;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
*
* https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/qr-sdk-documentation
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/8 21:11
*/
public class FeiShuAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* Creates a new instance
*
* @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to
* determine if authentication is required. Cannot be null.
*/
protected FeiShuAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
/**
* qq
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletRequest}
* @return {@link HttpServletRequest}
* @throws AuthenticationException AuthenticationException
* @throws IOException IOException
* @throws ServletException ServletException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException,
ServletException {
//@formatter:off
//@formatter:on
return null;
}
}

View File

@ -0,0 +1,163 @@
/*
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.feishu.filter;
import java.io.IOException;
import java.util.*;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.lang.NonNull;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Maps;
import cn.topiam.employee.authentication.feishu.FeiShuIdpScanCodeConfig;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.RESPONSE_TYPE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.FEISHU_OAUTH;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.feishu.constant.FeiShuAuthenticationConstants.*;
import static cn.topiam.employee.authentication.feishu.filter.FeiShuLoginAuthenticationFilter.getLoginUrl;
/**
*
*
* https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/qr-sdk-documentation
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/8 21:11
*/
public class FeiShuAuthorizationRequestGetFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory
.getLogger(FeiShuAuthorizationRequestGetFilter.class);
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher FEI_SHU_SCAN_CODE_REQUEST_MATCHER = new AntPathRequestMatcher(
FEISHU_OAUTH.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
*
*/
private final AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator(
Base64.getUrlEncoder());
private final IdentityProviderRepository identityProviderRepository;
public FeiShuAuthorizationRequestGetFilter(IdentityProviderRepository identityProviderRepository) {
this.identityProviderRepository = identityProviderRepository;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws IOException,
ServletException {
RequestMatcher.MatchResult matcher = FEI_SHU_SCAN_CODE_REQUEST_MATCHER.matcher(request);
if (!matcher.isMatch()) {
filterChain.doFilter(request, response);
return;
}
Map<String, String> variables = matcher.getVariables();
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
throw new NullPointerException("未查询到身份提供商信息");
}
IdentityProviderEntity entity = optional.get();
FeiShuIdpScanCodeConfig config = JSONObject.parseObject(entity.getConfig(),
FeiShuIdpScanCodeConfig.class);
Assert.notNull(config, "飞书扫码登录配置不能为空");
//构建授权请求
//@formatter:off
HashMap<@Nullable String, @Nullable Object> attributes = Maps.newHashMap();
attributes.put(RESPONSE_TYPE, CODE);
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode()
.clientId(config.getAppId())
.authorizationUri(AUTHORIZATION_REQUEST)
.redirectUri(getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey())
.attributes(attributes);
//@formatter:on
builder.parameters(parameters -> {
HashMap<String, Object> linkedParameters = new LinkedHashMap<>();
parameters.forEach((key, value) -> {
if (OAuth2ParameterNames.CLIENT_ID.equals(key)) {
linkedParameters.put(CLIENT_ID, value);
}
if (OAuth2ParameterNames.STATE.equals(key)) {
linkedParameters.put(OAuth2ParameterNames.STATE, value);
}
if (OAuth2ParameterNames.REDIRECT_URI.equals(key)) {
linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, value);
}
if (RESPONSE_TYPE.equals(key)) {
linkedParameters.put(RESPONSE_TYPE, value);
}
});
parameters.clear();
parameters.putAll(linkedParameters);
});
this.sendRedirectForAuthorization(request, response, builder.build());
}
private void sendRedirectForAuthorization(HttpServletRequest request,
HttpServletResponse response,
OAuth2AuthorizationRequest authorizationRequest) throws IOException {
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request,
response);
this.authorizationRedirectStrategy.sendRedirect(request, response,
authorizationRequest.getAuthorizationRequestUri());
}
/**
*
*/
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
public static RequestMatcher getRequestMatcher() {
return FEI_SHU_SCAN_CODE_REQUEST_MATCHER;
}
}

View File

@ -0,0 +1,152 @@
/*
* eiam-authentication-feishu - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.feishu.filter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.message.BasicHeader;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.nimbusds.oauth2.sdk.GrantType;
import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthenticationProcessingFilter;
import cn.topiam.employee.authentication.common.modal.IdpUser;
import cn.topiam.employee.authentication.common.service.UserIdpService;
import cn.topiam.employee.authentication.feishu.FeiShuIdpScanCodeConfig;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.util.HttpClientUtils;
import static cn.topiam.employee.authentication.common.IdentityProviderType.FEISHU_OAUTH;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.feishu.constant.FeiShuAuthenticationConstants.*;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/8 21:11
*/
public class FeiShuLoginAuthenticationFilter extends AbstractIdpAuthenticationProcessingFilter {
public final static String DEFAULT_FILTER_PROCESSES_URI = FEISHU_OAUTH
.getLoginPathPrefix() + "/*";
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
FEISHU_OAUTH.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", HttpMethod.GET.name());
/**
* Creates a new instance
*
* @param identityProviderRepository the {@link IdentityProviderRepository}
* @param authenticationUserDetails {@link UserIdpService}
*/
public FeiShuLoginAuthenticationFilter(IdentityProviderRepository identityProviderRepository,
UserIdpService authenticationUserDetails) {
super(DEFAULT_FILTER_PROCESSES_URI, authenticationUserDetails, identityProviderRepository);
}
/**
*
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletRequest}
* @return {@link HttpServletRequest}
* @throws AuthenticationException {@link AuthenticationException} AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException {
OAuth2AuthorizationRequest authorizationRequest = getOAuth2AuthorizationRequest(request,
response);
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerCode = variables.get(PROVIDER_CODE);
//code
String code = request.getParameter(OAuth2ParameterNames.CODE);
if (StringUtils.isEmpty(code)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_CODE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// state
String state = request.getParameter(OAuth2ParameterNames.STATE);
if (StringUtils.isEmpty(state)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
if (!authorizationRequest.getState().equals(state)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
//获取身份提供商
IdentityProviderEntity provider = getIdentityProviderEntity(providerCode);
FeiShuIdpScanCodeConfig config = JSONObject.parseObject(provider.getConfig(),
FeiShuIdpScanCodeConfig.class);
if (Objects.isNull(config)) {
logger.error("未查询到飞书扫码登录配置");
//无效身份提供商
OAuth2Error oauth2Error = new OAuth2Error(
AbstractIdpAuthenticationProcessingFilter.INVALID_IDP_CONFIG);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
//获取access token
HashMap<String, String> param = new HashMap<>(16);
param.put(CLIENT_ID, config.getAppId());
param.put(CLIENT_SECRET, config.getAppSecret());
param.put(OAuth2ParameterNames.CODE, code);
param.put(OAuth2ParameterNames.REDIRECT_URI, getLoginUrl(provider.getCode()));
param.put(OAuth2ParameterNames.GRANT_TYPE, GrantType.AUTHORIZATION_CODE.getValue());
JSONObject result = JSON.parseObject(HttpClientUtils.post(ACCESS_TOKEN, param));
// 获取user信息
param = new HashMap<>(16);
BasicHeader authorization = new BasicHeader(
"Authorization", result.getString(OAuth2ParameterNames.TOKEN_TYPE) + " "
+ result.getString(OAuth2ParameterNames.ACCESS_TOKEN));
result = JSON.parseObject(HttpClientUtils.get(USER_INFO, param, authorization));
// 返回
IdpUser idpUser = IdpUser.builder().openId(result.getString(OPEN_ID)).build();
return attemptAuthentication(request, response, FEISHU_OAUTH, providerCode, idpUser);
}
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl() + FEISHU_OAUTH.getLoginPathPrefix()
+ "/" + providerId;
return url.replaceAll("(?<!(http:|https:))/+", "/");
}
public static RequestMatcher getRequestMatcher() {
return REQUEST_MATCHER;
}
}

View File

@ -0,0 +1,98 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
/**
* Mfa Authentication Configurer
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/10 22:58
*/
public final class MfaAuthenticationConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, MfaAuthenticationConfigurer<H>, MfaAuthenticationFilter> {
private final OtpContextHelp otpContextHelp;
private final MfaAuthenticationHandler mfaAuthenticationHandler;
public MfaAuthenticationConfigurer(OtpContextHelp otpContextHelp,
AuthenticationSuccessHandler successHandler,
AuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(otpContextHelp, "otpContextHelp must not be null");
Assert.notNull(successHandler, "successHandler must not be null");
Assert.notNull(authenticationFailureHandler,
"authenticationFailureHandler must not be null");
this.otpContextHelp = otpContextHelp;
mfaAuthenticationHandler = new MfaAuthenticationHandler(successHandler,
authenticationFailureHandler);
}
/**
* Create the {@link RequestMatcher} given a loginProcessingUrl
*
* @param loginProcessingUrl creates the {@link RequestMatcher} based upon the
* loginProcessingUrl
* @return the {@link RequestMatcher} to use based upon the loginProcessingUrl
*/
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl);
}
@Override
public void init(H http) throws Exception {
//设置登录成功失败处理器
super.successHandler(mfaAuthenticationHandler);
super.failureHandler(mfaAuthenticationHandler);
//MFA认证
MfaAuthenticationFilter loginAuthenticationFilter = new MfaAuthenticationFilter();
this.setAuthenticationFilter(loginAuthenticationFilter);
//处理URL
super.loginProcessingUrl(MfaAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
super.init(http);
}
@Override
public void configure(H http) throws Exception {
//Mfa认证方式
http.addFilterBefore(new MfaAuthenticationMfaFactorsFilter(),
UsernamePasswordAuthenticationFilter.class);
//Mfa认证方式
http.addFilterAfter(new MfaAuthenticationSendOtpFilter(otpContextHelp),
MfaAuthenticationMfaFactorsFilter.class);
//Mfa认证
http.addFilterAfter(this.getAuthenticationFilter(),
MfaAuthenticationMfaFactorsFilter.class);
super.configure(http);
}
public static RequestMatcher getRequestMatcher() {
return MfaAuthenticationFilter.getRequestMatcher();
}
}

View File

@ -0,0 +1,141 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import cn.topiam.employee.authentication.mfa.email.EmailOtpProviderValidator;
import cn.topiam.employee.authentication.mfa.sms.SmsOtpProviderValidator;
import cn.topiam.employee.authentication.mfa.totp.TotpProviderValidator;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MfaFactor;
import cn.topiam.employee.core.security.mfa.MfaAuthentication;
import cn.topiam.employee.core.security.mfa.exception.MfaRequiredException;
import cn.topiam.employee.core.security.util.UserUtils;
import static cn.topiam.employee.authentication.mfa.constant.MfaAuthenticationConstants.MFA_VALIDATE;
import static cn.topiam.employee.common.enums.MfaFactor.SMS_OTP;
/**
* MFA
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/29 22:23
*/
public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final Logger logger = LoggerFactory
.getLogger(MfaAuthenticationFilter.class);
public static final String SPRING_SECURITY_FORM_CODE_KEY = "otp";
public static final String SPRING_SECURITY_FORM_TOTP_KEY = "totp";
public static final String SPRING_SECURITY_FORM_TYPE_KEY = "type";
public final static String DEFAULT_FILTER_PROCESSES_URI = MFA_VALIDATE;
public static final RequestMatcher MFA_LOGIN_MATCHER = new AntPathRequestMatcher(
DEFAULT_FILTER_PROCESSES_URI, HttpMethod.POST.name());
protected MfaAuthenticationFilter() {
super(MFA_LOGIN_MATCHER);
}
protected static RequestMatcher getRequestMatcher() {
return MFA_LOGIN_MATCHER;
}
/**
* Performs actual authentication.
* <p>
* The implementation should do one of the following:
* <ol>
* <li>Return a populated authentication token for the authenticated user, indicating
* successful authentication</li>
* <li>Return null, indicating that the authentication process is still in progress.
* Before returning, the implementation should perform any additional work required to
* complete the process.</li>
* <li>Throw an <tt>AuthenticationException</tt> if the authentication process
* fails</li>
* </ol>
*
* @param request from which to extract parameters and perform the authentication
* @param response the response, which may be needed if the implementation has to do a
* redirect as part of a multi-stage authentication process (such as OpenID).
* @return the authenticated user token, or null if authentication is incomplete.
* @throws AuthenticationException if authentication fails.
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
UserEntity user = UserUtils.getUser();
MfaAuthentication authentication = (MfaAuthentication) SecurityContextHolder.getContext()
.getAuthentication();
boolean result = false;
//获取类型
MfaFactor type = MfaFactor.getType(request.getParameter(SPRING_SECURITY_FORM_TYPE_KEY));
if (Objects.isNull(type)) {
throw new MfaRequiredException("MFA 类型不存在");
}
//SMS OPT
if (SMS_OTP.equals(type)) {
String otp = request.getParameter(SPRING_SECURITY_FORM_CODE_KEY);
if (StringUtils.isBlank(otp)) {
throw new MfaRequiredException("OTP 参数不存在");
}
result = smsOtpProviderValidator.validate(otp);
}
//Mail OPT
if (MfaFactor.EMAIL_OTP.equals(type)) {
String otp = request.getParameter(SPRING_SECURITY_FORM_CODE_KEY);
if (StringUtils.isBlank(otp)) {
throw new MfaRequiredException("OTP 参数不存在");
}
result = emailOtpProviderValidator.validate(otp);
}
//TOTP
if (MfaFactor.APP_TOTP.equals(type)) {
long totp = Long.parseLong(request.getParameter(SPRING_SECURITY_FORM_TOTP_KEY));
result = totpProviderValidator.validate(String.valueOf(totp));
}
if (!result) {
logger.error("用户ID: [{}] 用户名: [{}] {} 认证失败", type.getDesc(), user.getId(),
user.getUsername());
return authentication;
}
logger.error("用户ID: [{}] 用户名: [{}] {} 认证成功", type.getDesc(), user.getId(),
user.getUsername());
//认证成功
authentication.setValidated(true);
return authentication;
}
protected final EmailOtpProviderValidator emailOtpProviderValidator = new EmailOtpProviderValidator();
protected final SmsOtpProviderValidator smsOtpProviderValidator = new SmsOtpProviderValidator();
protected final TotpProviderValidator totpProviderValidator = new TotpProviderValidator();
}

View File

@ -0,0 +1,147 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.Assert;
import cn.topiam.employee.common.constants.AuthorizeConstants;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.core.security.mfa.MfaAuthentication;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.HttpResponseUtils;
import cn.topiam.employee.support.util.HttpUrlUtils;
import static cn.topiam.employee.core.context.SettingContextHelp.isMfaEnabled;
import static cn.topiam.employee.support.constant.EiamConstants.CAPTCHA_CODE_SESSION;
import static cn.topiam.employee.support.constant.EiamConstants.SAVED_REQUEST;
import static cn.topiam.employee.support.context.ServletContextHelp.acceptIncludeTextHtml;
import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX000102;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/28 23:36
*/
@SuppressWarnings("DuplicatedCode")
public class MfaAuthenticationHandler implements AuthenticationSuccessHandler,
AuthenticationFailureHandler {
private static final String REQUIRE_MFA = "require_mfa";
private final AuthenticationSuccessHandler successHandler;
private final AuthenticationFailureHandler failureHandler;
public MfaAuthenticationHandler(AuthenticationSuccessHandler successHandler,
AuthenticationFailureHandler failureHandler) {
Assert.notNull(successHandler, "userIdpService must not be null");
Assert.notNull(failureHandler, "userIdpService must not be null");
this.successHandler = successHandler;
this.failureHandler = failureHandler;
}
/**
* Called when an authentication attempt fails.
*
* @param request the request during which the authentication attempt occurred.
* @param response the response.
* @param exception the exception which was thrown to reject the authentication
* request.
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException,
ServletException {
failureHandler.onAuthenticationFailure(request, response, exception);
}
/**
* Called when a user has been successfully authenticated.
*
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during
* the authentication process.
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException,
ServletException {
boolean isTextHtml = acceptIncludeTextHtml(request);
//TODO MFA启用、但是对象非MFA说明需要MFA认证
if (isMfaEnabled() && !(authentication instanceof MfaAuthentication)) {
SecurityContextHolder.getContext()
.setAuthentication(new MfaAuthentication(authentication));
//Clear Authentication Attributes
clearAuthenticationAttributes(request);
if (response.isCommitted()) {
return;
}
if (!isTextHtml) {
HttpResponseUtils.flushResponseJson(response, HttpStatus.BAD_REQUEST.value(),
ApiRestResult.builder().status(REQUIRE_MFA).message(REQUIRE_MFA).build());
return;
}
//跳转登录,前端会有接口获取状态,并进行展示 MFA
response.sendRedirect(HttpUrlUtils
.format(ServerContextHelp.getPortalPublicBaseUrl() + AuthorizeConstants.FE_LOGIN));
return;
}
//TODO Mfa 验证成功
if (authentication instanceof MfaAuthentication
&& ((MfaAuthentication) authentication).getValidated()) {
SecurityContextHolder.getContext()
.setAuthentication(((MfaAuthentication) authentication).getFirst());
successHandler.onAuthenticationSuccess(request, response, authentication);
return;
}
//TODO Mfa 验证失败
if (authentication instanceof MfaAuthentication
&& !((MfaAuthentication) authentication).getValidated()) {
HttpResponseUtils.flushResponseJson(response, HttpStatus.BAD_REQUEST.value(),
ApiRestResult.builder().status(EX000102.getCode()).message(EX000102.getMessage())
.build());
return;
}
successHandler.onAuthenticationSuccess(request, response, authentication);
}
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
//清理验证码
session.removeAttribute(CAPTCHA_CODE_SESSION);
//清理保存请求
session.removeAttribute(SAVED_REQUEST);
//清理认证异常
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
}

View File

@ -0,0 +1,132 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MfaFactor;
import cn.topiam.employee.core.security.util.UserUtils;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.DesensitizationUtil;
import cn.topiam.employee.support.util.HttpResponseUtils;
import lombok.Builder;
import lombok.Data;
import static cn.topiam.employee.authentication.mfa.constant.MfaAuthenticationConstants.LOGIN_MFA_FACTORS;
import static cn.topiam.employee.core.context.SettingContextHelp.getMfaFactors;
/**
* MfaAuthenticationMfaFactorsFilter
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/2 13:28
*/
public class MfaAuthenticationMfaFactorsFilter extends OncePerRequestFilter {
public final static String DEFAULT_FILTER_PROCESSES_URI = LOGIN_MFA_FACTORS;
public static final RequestMatcher LOGIN_MFA_FACTORS_MATCHER = new AntPathRequestMatcher(
DEFAULT_FILTER_PROCESSES_URI, HttpMethod.GET.name());
@Override
@SuppressWarnings("AlibabaAvoidComplexCondition")
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException,
IOException {
if (!getRequestMatcher().matches(request)) {
filterChain.doFilter(request, response);
return;
}
UserEntity user = UserUtils.getUser();
List<MfaFactorResult> list = new ArrayList<>();
List<MfaFactor> factors = getMfaFactors();
for (MfaFactor provider : factors) {
MfaFactorResult result = MfaFactorResult.builder().build();
result.setFactor(provider);
result.setUsable(false);
//sms
if (provider.equals(MfaFactor.SMS_OTP) && StringUtils.isNotBlank(user.getPhone())) {
result.setTarget(DesensitizationUtil.phoneEncrypt(user.getPhone()));
result.setUsable(true);
}
//otp
if (provider.equals(MfaFactor.EMAIL_OTP) && StringUtils.isNotBlank(user.getEmail())) {
result.setTarget(DesensitizationUtil.emailEncrypt(user.getEmail()));
result.setUsable(true);
}
//totp
if (provider.equals(MfaFactor.APP_TOTP)
&& (!Objects.isNull(user.getTotpBind()) && user.getTotpBind())) {
result.setUsable(true);
}
list.add(result);
}
HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(),
ApiRestResult.ok(list));
}
public static RequestMatcher getRequestMatcher() {
return LOGIN_MFA_FACTORS_MATCHER;
}
/**
* Mfa
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/13 21:29
*/
@Builder
@Data
public static class MfaFactorResult implements Serializable {
@Serial
private static final long serialVersionUID = 7255002979319970337L;
/**
* provider
*/
private MfaFactor factor;
/**
*
*/
private Boolean usable;
/**
*
*/
private String target;
}
}

View File

@ -0,0 +1,159 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.MessageNoticeChannel;
import cn.topiam.employee.common.enums.SmsType;
import cn.topiam.employee.common.exception.LoginOtpActionNotSupportException;
import cn.topiam.employee.common.util.RequestUtils;
import cn.topiam.employee.core.security.mfa.MfaAuthentication;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.core.security.userdetails.UserDetails;
import cn.topiam.employee.core.security.util.SecurityUtils;
import cn.topiam.employee.core.security.util.UserUtils;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.HttpResponseUtils;
import cn.topiam.employee.support.validation.ValidationHelp;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import static cn.topiam.employee.authentication.mfa.constant.MfaAuthenticationConstants.OTP_SEND_OTP;
/**
* OPT
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/1 22:01
*/
public class MfaAuthenticationSendOtpFilter extends OncePerRequestFilter {
public final static String DEFAULT_FILTER_PROCESSES_URI = OTP_SEND_OTP;
public static final RequestMatcher SMS_SEND_OPT_MATCHER = new AntPathRequestMatcher(
DEFAULT_FILTER_PROCESSES_URI, HttpMethod.POST.name());
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException,
IOException {
if (!getRequestMatcher().matches(request)) {
filterChain.doFilter(request, response);
return;
}
SecurityContext securityContext = SecurityUtils.getSecurityContext();
Authentication authentication = securityContext.getAuthentication();
//非MFA对象
if (!(authentication instanceof MfaAuthentication)) {
HttpResponseUtils.flushResponseJson(response, HttpStatus.UNAUTHORIZED.value(),
ApiRestResult.ok());
return;
}
Map<String, Object> params = RequestUtils.getParams(request);
String value = OBJECT_MAPPER.writeValueAsString(params);
SendOtpRequest sendOtpRequest = OBJECT_MAPPER.readValue(value, SendOtpRequest.class);
ValidationHelp.ValidationResult<SendOtpRequest> validationResult = ValidationHelp
.validateEntity(sendOtpRequest);
if (validationResult.isHasErrors()) {
throw new ConstraintViolationException(validationResult.getConstraintViolations());
}
//MFA从会话上下文中获取手机号及邮箱信息
UserDetails principal = (UserDetails) ((MfaAuthentication) authentication).getFirst()
.getPrincipal();
UserEntity user = UserUtils.getUser(principal.getId());
String email = user.getEmail();
if (MessageNoticeChannel.MAIL.equals(sendOtpRequest.getChannel())) {
send(email, MessageNoticeChannel.MAIL);
HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(),
ApiRestResult.ok());
return;
}
String phone = user.getPhone();
if (MessageNoticeChannel.SMS.equals(sendOtpRequest.getChannel())) {
send(phone, MessageNoticeChannel.SMS);
HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(),
ApiRestResult.ok());
return;
}
throw new LoginOtpActionNotSupportException();
}
/**
*
*
* @param target {@link String}
* @param channel {@link MessageNoticeChannel}
*/
private void send(String target, MessageNoticeChannel channel) {
String type;
if (channel == MessageNoticeChannel.MAIL) {
type = MailType.AGAIN_VERIFY.getCode();
} else {
type = SmsType.AGAIN_VERIFY.getCode();
}
otpContextHelp.sendOtp(target, type, channel);
}
/**
* OTP
*/
@Data
public static class SendOtpRequest implements Serializable {
/**
*
*/
@Parameter(description = "channel")
@javax.validation.constraints.NotNull(message = "消息渠道不能为空")
private MessageNoticeChannel channel;
}
public static RequestMatcher getRequestMatcher() {
return SMS_SEND_OPT_MATCHER;
}
private final OtpContextHelp otpContextHelp;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public MfaAuthenticationSendOtpFilter(OtpContextHelp otpContextHelp) {
this.otpContextHelp = otpContextHelp;
}
}

View File

@ -0,0 +1,48 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa.constant;
import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH;
/**
* Mfa
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/19 23:19
*/
public final class MfaAuthenticationConstants {
/**
* mfa
*/
public static final String LOGIN_MFA = LOGIN_PATH + "/mfa";
/**
* mfa
*/
public static final String LOGIN_MFA_FACTORS = LOGIN_MFA + "/factors";
/**
* maf
*/
public static final String MFA_VALIDATE = LOGIN_MFA + "/validate";
/**
* OTP
*/
public static final String OTP_SEND_OTP = LOGIN_MFA + "/send";
}

View File

@ -1,5 +1,5 @@
/*
* eiam-authentication-sms - Employee Identity and Access Management Program
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.configurer;
package cn.topiam.employee.authentication.mfa.constant;

View File

@ -0,0 +1,47 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa.email;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.MessageNoticeChannel;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.core.security.util.UserUtils;
import cn.topiam.employee.support.context.ApplicationContextHelp;
/**
* OTP
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
*/
public class EmailOtpProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
UserEntity user = UserUtils.getUser();
OtpContextHelp bean = ApplicationContextHelp.getBean(OtpContextHelp.class);
return bean.checkOtp(MailType.AGAIN_VERIFY.getCode(), MessageNoticeChannel.MAIL,
user.getEmail(), code);
}
}

View File

@ -0,0 +1,47 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa.sms;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MessageNoticeChannel;
import cn.topiam.employee.common.enums.SmsType;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.core.security.util.UserUtils;
import cn.topiam.employee.support.context.ApplicationContextHelp;
/**
* OTP
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
*/
public class SmsOtpProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
UserEntity user = UserUtils.getUser();
OtpContextHelp bean = ApplicationContextHelp.getBean(OtpContextHelp.class);
return bean.checkOtp(SmsType.AGAIN_VERIFY.getCode(), MessageNoticeChannel.SMS,
user.getPhone(), code);
}
}

View File

@ -0,0 +1,45 @@
/*
* eiam-authentication-mfa - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.mfa.totp;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.core.security.mfa.provider.TotpAuthenticator;
import cn.topiam.employee.core.security.util.UserUtils;
/**
* Totp
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
*/
public class TotpProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
UserEntity user = UserUtils.getUser();
return totpAuthenticator.checkCode(user.getSharedSecret(), Long.parseLong(code),
System.currentTimeMillis());
}
private final TotpAuthenticator totpAuthenticator = new TotpAuthenticator();
}

View File

@ -49,8 +49,9 @@ import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.authentication.qq.QqIdpOauthConfig;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import static cn.topiam.employee.authentication.common.IdentityProviderType.QQ;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.qq.filter.QqOAuth2LoginAuthenticationFilter.getLoginUrl;
import static cn.topiam.employee.common.enums.IdentityProviderType.QQ;
import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_AUTHORIZE;
/**
@ -64,16 +65,12 @@ public class QqOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFi
private final Logger logger = LoggerFactory
.getLogger(QqOAuth2AuthorizationRequestRedirectFilter.class);
/**
* ID
*/
public static final String PROVIDER_ID = "providerId";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher QQ_REQUEST_MATCHER = new AntPathRequestMatcher(
QQ.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name());
QQ.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", HttpMethod.GET.name());
/**
*
@ -104,9 +101,9 @@ public class QqOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFi
return;
}
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
throw new NullPointerException("未查询到身份提供商信息");
}
@ -117,7 +114,8 @@ public class QqOAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFi
//构建授权请求
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode()
.clientId(config.getAppId()).authorizationUri(URL_AUTHORIZE)
.redirectUri(getLoginUrl(providerId)).state(DEFAULT_STATE_GENERATOR.generateKey());
.redirectUri(getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey());
builder.parameters(parameters -> {
parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2ParameterNames.CODE);
});

View File

@ -53,8 +53,8 @@ import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.support.util.HttpClientUtils;
import static com.nimbusds.oauth2.sdk.GrantType.AUTHORIZATION_CODE;
import static cn.topiam.employee.authentication.qq.filter.QqOAuth2AuthorizationRequestRedirectFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.QQ;
import static cn.topiam.employee.authentication.common.IdentityProviderType.QQ;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_ACCESS_TOKEN;
import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstants.URL_GET_OPEN_ID;
@ -67,10 +67,10 @@ import static cn.topiam.employee.portal.idp.qq.constant.QqAuthenticationConstant
@SuppressWarnings({ "AlibabaClassNamingShouldBeCamel", "DuplicatedCode" })
public class QqOAuth2LoginAuthenticationFilter extends AbstractIdpAuthenticationProcessingFilter {
final String ERROR_CODE = "error";
public static final String DEFAULT_FILTER_PROCESSES_URI = QQ.getLoginPathPrefix()
public final static String DEFAULT_FILTER_PROCESSES_URI = QQ.getLoginPathPrefix()
+ "/*";
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
QQ.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}", HttpMethod.GET.name());
QQ.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", HttpMethod.GET.name());
/**
* Creates a new instance
@ -100,7 +100,7 @@ public class QqOAuth2LoginAuthenticationFilter extends AbstractIdpAuthentication
TraceUtils.put(UUID.randomUUID().toString());
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerId = variables.get(PROVIDER_CODE);
//code
String code = request.getParameter(OAuth2ParameterNames.CODE);
if (StringUtils.isEmpty(code)) {
@ -133,7 +133,7 @@ public class QqOAuth2LoginAuthenticationFilter extends AbstractIdpAuthentication
param.put(OAuth2ParameterNames.CLIENT_ID, config.getAppId().trim());
param.put(OAuth2ParameterNames.CLIENT_SECRET, config.getAppKey().trim());
param.put(OAuth2ParameterNames.CODE, code.trim());
param.put(OAuth2ParameterNames.REDIRECT_URI, getLoginUrl(providerId));
param.put(OAuth2ParameterNames.REDIRECT_URI, getLoginUrl(provider.getCode()));
param.put("fmt", "json");
//注意QQ不能使用编码后的get请求否则会报 {"error_description":"redirect uri is illegal","error":100010}
JSONObject result = JSON.parseObject(HttpClientUtils.doGet(URL_GET_ACCESS_TOKEN, param));

View File

@ -0,0 +1,94 @@
/*
* eiam-authentication-sms - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import cn.topiam.employee.authentication.sms.exception.PhoneNotExistException;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.HttpResponseUtils;
import static cn.topiam.employee.authentication.sms.constant.SmsAuthenticationConstants.PHONE_KEY;
import static cn.topiam.employee.authentication.sms.constant.SmsAuthenticationConstants.SMS_SEND_OTP;
import static cn.topiam.employee.common.enums.MessageNoticeChannel.SMS;
import static cn.topiam.employee.common.enums.SmsType.LOGIN;
/**
* OPT
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/1 22:01
*/
public class SendSmsCaptchaFilter extends OncePerRequestFilter {
public final static String DEFAULT_FILTER_PROCESSES_URI = SMS_SEND_OTP;
public static final RequestMatcher SMS_SEND_OPT_MATCHER = new AntPathRequestMatcher(
DEFAULT_FILTER_PROCESSES_URI, HttpMethod.POST.name());
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException,
IOException {
if (!getRequestMatcher().matches(request)) {
filterChain.doFilter(request, response);
return;
}
String phone = request.getParameter(PHONE_KEY);
if (StringUtils.isBlank(phone)) {
throw new PhoneNotExistException();
}
//判断是否存在用户
UserEntity user = userRepository.findByPhone(phone);
if (Objects.isNull(user)) {
HttpResponseUtils.flushResponseJson(response, HttpStatus.OK.value(),
ApiRestResult.ok());
return;
}
//发送OPT
otpContextHelp.sendOtp(phone, LOGIN.getCode(), SMS);
}
public static RequestMatcher getRequestMatcher() {
return SMS_SEND_OPT_MATCHER;
}
private final UserRepository userRepository;
private final OtpContextHelp otpContextHelp;
public SendSmsCaptchaFilter(UserRepository userRepository, OtpContextHelp otpContextHelp) {
this.userRepository = userRepository;
this.otpContextHelp = otpContextHelp;
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.configurer;
package cn.topiam.employee.authentication.sms;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@ -25,7 +25,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import cn.topiam.employee.authentication.sms.filter.SmsAuthenticationFilter;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
/**
*
@ -35,11 +36,20 @@ import cn.topiam.employee.authentication.sms.filter.SmsAuthenticationFilter;
*/
public final class SmsAuthenticationConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, SmsAuthenticationConfigurer<H>, SmsAuthenticationFilter> {
private final UserRepository userRepository;
private final UserDetailsService userDetailsService;
public SmsAuthenticationConfigurer(UserDetailsService userDetailsService) {
private final OtpContextHelp otpContextHelp;
public SmsAuthenticationConfigurer(UserRepository userRepository,
UserDetailsService userDetailsService,
OtpContextHelp otpContextHelp) {
Assert.notNull(userDetailsService, "userRepository must not be null");
Assert.notNull(userDetailsService, "userDetailsService must not be null");
Assert.notNull(otpContextHelp, "otpContextHelp must not be null");
this.userDetailsService = userDetailsService;
this.userRepository = userRepository;
this.otpContextHelp = otpContextHelp;
}
/**
@ -58,7 +68,7 @@ public final class SmsAuthenticationConfigurer<H extends HttpSecurityBuilder<H>>
public void init(H http) throws Exception {
//SMS
SmsAuthenticationFilter loginAuthenticationFilter = new SmsAuthenticationFilter(
userDetailsService);
userDetailsService, otpContextHelp);
this.setAuthenticationFilter(loginAuthenticationFilter);
//处理URL
super.loginProcessingUrl(SmsAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI);
@ -67,12 +77,10 @@ public final class SmsAuthenticationConfigurer<H extends HttpSecurityBuilder<H>>
@Override
public void configure(H http) throws Exception {
http.addFilterAfter(this.getAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
SendSmsCaptchaFilter sendSmsCaptchaFilter = new SendSmsCaptchaFilter(userRepository,
otpContextHelp);
http.addFilterAfter(sendSmsCaptchaFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(this.getAuthenticationFilter(), SendSmsCaptchaFilter.class);
super.configure(http);
}
public RequestMatcher getRequestMatcher() {
return SmsAuthenticationFilter.getRequestMatcher();
}
}

View File

@ -15,12 +15,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.filter;
package cn.topiam.employee.authentication.sms;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationServiceException;
@ -28,15 +30,23 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import cn.topiam.employee.authentication.sms.exception.CaptchaNotExistException;
import cn.topiam.employee.authentication.sms.exception.PhoneNotExistException;
import cn.topiam.employee.common.enums.MessageNoticeChannel;
import cn.topiam.employee.core.security.authentication.SmsAuthentication;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.HttpResponseUtils;
import static cn.topiam.employee.authentication.sms.constant.SmsAuthenticationConstants.CODE_KEY;
import static cn.topiam.employee.authentication.sms.constant.SmsAuthenticationConstants.PHONE_KEY;
import static cn.topiam.employee.common.constants.AuthorizeConstants.SMS_LOGIN;
import static cn.topiam.employee.common.enums.SmsType.LOGIN;
import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX000102;
/**
@ -47,28 +57,25 @@ import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX00010
*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String PHONE_KEY = "phone";
private final Logger logger = LoggerFactory
.getLogger(SmsAuthenticationFilter.class);
/**
*
*/
public static final String METHOD = "POST";
private String phoneParameter = PHONE_KEY;
private String codeParameter = CODE_KEY;
/**
* POST
*/
private boolean postOnly = true;
public static final String DEFAULT_FILTER_PROCESSES_URI = SMS_LOGIN;
public final static String DEFAULT_FILTER_PROCESSES_URI = SMS_LOGIN;
public static final RequestMatcher SMS_LOGIN_MATCHER = new AntPathRequestMatcher(
DEFAULT_FILTER_PROCESSES_URI, HttpMethod.POST.name());
public SmsAuthenticationFilter(UserDetailsService userDetailsService) {
super(SMS_LOGIN_MATCHER);
this.userDetailsService = userDetailsService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
@ -79,7 +86,21 @@ public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFil
}
// 获取手机号
String phone = StringUtils.defaultString(obtainUsername(request), "").trim();
if (StringUtils.isBlank(phone)) {
throw new PhoneNotExistException();
}
String code = StringUtils.defaultString(obtainCode(request), "").trim();
if (StringUtils.isBlank(code)) {
throw new CaptchaNotExistException();
}
UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
//判断短信验证码
Boolean checkOtp = otpContextHelp.checkOtp(LOGIN.getCode(), MessageNoticeChannel.SMS,
phone, code);
if (!checkOtp) {
logger.error("用户手机号: [{}], 验证码: [{}] 认证失败", phone, code);
throw new UsernameNotFoundException("用户名或密码错误");
}
SmsAuthentication authentication = new SmsAuthentication(userDetails, phone,
userDetails.getAuthorities());
// Allow subclasses to set the "details" property
@ -106,6 +127,10 @@ public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFil
return request.getParameter(phoneParameter);
}
protected String obtainCode(HttpServletRequest request) {
return request.getParameter(codeParameter);
}
/**
* Provided so that subclasses may configure what is put into the
* authentication request's details property.
@ -135,9 +160,18 @@ public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFil
return phoneParameter;
}
public void setPhoneParameter(String phoneParameter) {
public final String getCodeParameter() {
return codeParameter;
}
public void setPhoneParameter(String codeParameter) {
Assert.hasText(phoneParameter, "Mobile parameter must not be empty or null");
this.phoneParameter = phoneParameter;
this.codeParameter = codeParameter;
}
public void setCodeParameter(String codeParameter) {
Assert.hasText(codeParameter, "Code parameter must not be empty or null");
this.codeParameter = codeParameter;
}
public static RequestMatcher getRequestMatcher() {
@ -145,4 +179,13 @@ public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFil
}
private final UserDetailsService userDetailsService;
private final OtpContextHelp otpContextHelp;
public SmsAuthenticationFilter(UserDetailsService userDetailsService,
OtpContextHelp otpContextHelp) {
super(SMS_LOGIN_MATCHER);
this.userDetailsService = userDetailsService;
this.otpContextHelp = otpContextHelp;
}
}

View File

@ -0,0 +1,43 @@
/*
* eiam-authentication-sms - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.constant;
import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH;
/**
* Sms
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/19 23:19
*/
public final class SmsAuthenticationConstants {
/**
* sms login
*/
public static final String SMS_LOGIN = LOGIN_PATH + "/sms";
/**
* OTP
*/
public static final String SMS_SEND_OTP = SMS_LOGIN + "/send";
public static final String PHONE_KEY = "phone";
public static final String CODE_KEY = "code";
}

View File

@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.filter;
package cn.topiam.employee.authentication.sms.constant;

View File

@ -0,0 +1,31 @@
/*
* eiam-authentication-sms - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.exception;
import cn.topiam.employee.support.exception.TopIamException;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/2 13:00
*/
public class CaptchaNotExistException extends TopIamException {
public CaptchaNotExistException() {
super("captcha_not_exist", "验证码不存在", DEFAULT_STATUS);
}
}

View File

@ -0,0 +1,33 @@
/*
* eiam-authentication-sms - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.authentication.sms.exception;
import cn.topiam.employee.support.exception.TopIamException;
/**
*
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/2 12:59
*/
public class PhoneNotExistException extends TopIamException {
public PhoneNotExistException() {
super("phone_not_exist", "手机号不存在", DEFAULT_STATUS);
}
}

View File

@ -51,11 +51,9 @@ import com.google.common.collect.Sets;
import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.APP_ID;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.AUTHORIZATION_REQUEST;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.HREF;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.SNSAPI_LOGIN;
import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_CODE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.WECHAT_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.*;
/**
*
@ -69,16 +67,11 @@ public class WeChatScanCodeAuthorizationRequestRedirectFilter extends OncePerReq
private final Logger logger = LoggerFactory
.getLogger(WeChatScanCodeAuthorizationRequestRedirectFilter.class);
/**
* ID
*/
public static final String PROVIDER_ID = "providerId";
/**
* AntPathRequestMatcher
*/
public static final AntPathRequestMatcher WE_CHAT_SCAN_CODE_REQUEST_MATCHER = new AntPathRequestMatcher(
WECHAT_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
WECHAT_QR.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
@ -110,9 +103,9 @@ public class WeChatScanCodeAuthorizationRequestRedirectFilter extends OncePerReq
return;
}
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
throw new NullPointerException("未查询到身份提供商信息");
}
@ -127,7 +120,7 @@ public class WeChatScanCodeAuthorizationRequestRedirectFilter extends OncePerReq
.clientId(config.getAppId())
.scopes(Sets.newHashSet(SNSAPI_LOGIN))
.authorizationUri(AUTHORIZATION_REQUEST)
.redirectUri(WeChatScanCodeLoginAuthenticationFilter.getLoginUrl(providerId))
.redirectUri(WeChatScanCodeLoginAuthenticationFilter.getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey())
.attributes(attributes);
//@formatter:on

View File

@ -44,21 +44,17 @@ import cn.topiam.employee.authentication.common.filter.AbstractIdpAuthentication
import cn.topiam.employee.authentication.common.modal.IdpUser;
import cn.topiam.employee.authentication.common.service.UserIdpService;
import cn.topiam.employee.authentication.wechat.WeChatIdpScanCodeConfig;
import cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.enums.IdentityProviderType;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.util.HttpClientUtils;
import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.ACCESS_TOKEN;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.APP_ID;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.ERROR_CODE;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.SECRET;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.USER_INFO;
import static cn.topiam.employee.authentication.wechat.filter.WeChatScanCodeAuthorizationRequestRedirectFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_CODE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.WECHAT_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.*;
/**
*
@ -69,11 +65,10 @@ import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_C
public class WeChatScanCodeLoginAuthenticationFilter extends
AbstractIdpAuthenticationProcessingFilter {
public static final String DEFAULT_FILTER_PROCESSES_URI = WECHAT_SCAN_CODE
public final static String DEFAULT_FILTER_PROCESSES_URI = WECHAT_QR
.getLoginPathPrefix() + "/*";
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
WECHAT_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
HttpMethod.GET.name());
WECHAT_QR.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}", HttpMethod.GET.name());
/**
* Creates a new instance
@ -102,7 +97,7 @@ public class WeChatScanCodeLoginAuthenticationFilter extends
response);
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerId = variables.get(PROVIDER_CODE);
//code
String code = request.getParameter(OAuth2ParameterNames.CODE);
if (StringUtils.isEmpty(code)) {
@ -136,7 +131,8 @@ public class WeChatScanCodeLoginAuthenticationFilter extends
param.put(SECRET, config.getAppSecret());
param.put(OAuth2ParameterNames.CODE, code);
param.put(OAuth2ParameterNames.GRANT_TYPE, AUTHORIZATION_CODE.getValue());
JSONObject result = JSON.parseObject(HttpClientUtils.get(ACCESS_TOKEN, param));
JSONObject result = JSON
.parseObject(HttpClientUtils.get(WeChatAuthenticationConstants.ACCESS_TOKEN, param));
if (result.containsKey(ERROR_CODE)) {
logger.error("获取access_token发生错误: " + result.toJSONString());
throw new TopIamException("获取access_token发生错误: " + result.toJSONString());
@ -146,20 +142,20 @@ public class WeChatScanCodeLoginAuthenticationFilter extends
param.put(OAuth2ParameterNames.ACCESS_TOKEN,
result.getString(OAuth2ParameterNames.ACCESS_TOKEN));
param.put(OidcScopes.OPENID, result.getString(OidcScopes.OPENID));
result = JSON.parseObject(HttpClientUtils.get(USER_INFO, param));
result = JSON
.parseObject(HttpClientUtils.get(WeChatAuthenticationConstants.USER_INFO, param));
if (result.containsKey(ERROR_CODE)) {
logger.error("获取微信用户个人信息发生错误: " + result.toJSONString());
throw new TopIamException("获取微信用户个人信息发生错误: " + result.toJSONString());
}
// 返回
IdpUser idpUser = IdpUser.builder().openId(param.get(OidcScopes.OPENID)).build();
return attemptAuthentication(request, response, IdentityProviderType.WECHAT_SCAN_CODE,
providerId, idpUser);
return attemptAuthentication(request, response, WECHAT_QR, providerId, idpUser);
}
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl()
+ WECHAT_SCAN_CODE.getLoginPathPrefix() + "/" + providerId;
String url = ServerContextHelp.getPortalPublicBaseUrl() + WECHAT_QR.getLoginPathPrefix()
+ "/" + providerId;
return url.replaceAll("(?<!(http:|https:))/+", "/");
}

View File

@ -24,14 +24,14 @@ package cn.topiam.employee.authentication.wechatwork.constant;
* Created by support@topiam.cn on 2021/12/9 22:19
*/
public final class WeChatWorkAuthenticationConstants {
public static final String APP_ID = "appid";
public static final String AGENT_ID = "agentid";
public static final String HREF = "href";
public static final String LOGIN_TYPE = "login_type";
public static final String JSSDK = "jssdk";
public static final String URL_AUTHORIZE = "https://open.work.weixin.qq.com/wwopen/sso/v1/qrConnect";
public final static String APP_ID = "appid";
public final static String AGENT_ID = "agentid";
public final static String HREF = "href";
public final static String LOGIN_TYPE = "login_type";
public final static String JSSDK = "jssdk";
public final static String URL_AUTHORIZE = "https://open.work.weixin.qq.com/wwopen/sso/v1/qrConnect";
public static final String GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo";
public final static String GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo";
public static final String GET_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
public final static String GET_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
}

View File

@ -49,7 +49,8 @@ import cn.topiam.employee.authentication.wechatwork.WeChatWorkIdpScanCodeConfig;
import cn.topiam.employee.authentication.wechatwork.constant.WeChatWorkAuthenticationConstants;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import static cn.topiam.employee.common.enums.IdentityProviderType.WECHATWORK_SCAN_CODE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.WECHAT_WORK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
/**
*
@ -63,13 +64,8 @@ public class WeChatWorkScanCodeAuthorizationRequestRedirectFilter extends OncePe
private final Logger logger = LoggerFactory
.getLogger(WeChatWorkScanCodeAuthorizationRequestRedirectFilter.class);
/**
* ID
*/
public static final String PROVIDER_ID = "providerId";
public static final AntPathRequestMatcher WECHAT_WORK_REQUEST_MATCHER = new AntPathRequestMatcher(
WECHATWORK_SCAN_CODE.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
WECHAT_WORK_QR.getAuthorizationPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
@ -101,9 +97,9 @@ public class WeChatWorkScanCodeAuthorizationRequestRedirectFilter extends OncePe
return;
}
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerCode = variables.get(PROVIDER_CODE);
Optional<IdentityProviderEntity> optional = identityProviderRepository
.findByIdAndEnabledIsTrue(Long.valueOf(providerId));
.findByCodeAndEnabledIsTrue(providerCode);
if (optional.isEmpty()) {
throw new NullPointerException("未查询到身份提供商信息");
}
@ -115,7 +111,8 @@ public class WeChatWorkScanCodeAuthorizationRequestRedirectFilter extends OncePe
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode()
.clientId(config.getCorpId())
.authorizationUri(WeChatWorkAuthenticationConstants.URL_AUTHORIZE)
.redirectUri(WeChatWorkScanCodeLoginAuthenticationFilter.getLoginUrl(providerId))
.redirectUri(
WeChatWorkScanCodeLoginAuthenticationFilter.getLoginUrl(optional.get().getCode()))
.state(DEFAULT_STATE_GENERATOR.generateKey());
builder.parameters(parameters -> {
HashMap<String, Object> linkedParameters = new LinkedHashMap<>();

View File

@ -53,8 +53,8 @@ import cn.topiam.employee.common.repository.authentication.IdentityProviderRepos
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.support.util.HttpClientUtils;
import static cn.topiam.employee.authentication.wechatwork.filter.WeChatWorkScanCodeAuthorizationRequestRedirectFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.WECHATWORK_SCAN_CODE;
import static cn.topiam.employee.authentication.common.IdentityProviderType.WECHAT_WORK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
/**
*
@ -67,10 +67,10 @@ public class WeChatWorkScanCodeLoginAuthenticationFilter extends
AbstractIdpAuthenticationProcessingFilter {
final String ERROR_CODE = "errcode";
final String SUCCESS = "0";
public static final String DEFAULT_FILTER_PROCESSES_URI = WECHATWORK_SCAN_CODE
public final static String DEFAULT_FILTER_PROCESSES_URI = WECHAT_WORK_QR
.getLoginPathPrefix() + "/*";
public static final AntPathRequestMatcher REQUEST_MATCHER = new AntPathRequestMatcher(
WECHATWORK_SCAN_CODE.getLoginPathPrefix() + "/" + "{" + PROVIDER_ID + "}",
WECHAT_WORK_QR.getLoginPathPrefix() + "/" + "{" + PROVIDER_CODE + "}",
HttpMethod.GET.name());
/**
@ -101,7 +101,7 @@ public class WeChatWorkScanCodeLoginAuthenticationFilter extends
TraceUtils.put(UUID.randomUUID().toString());
RequestMatcher.MatchResult matcher = REQUEST_MATCHER.matcher(request);
Map<String, String> variables = matcher.getVariables();
String providerId = variables.get(PROVIDER_ID);
String providerId = variables.get(PROVIDER_CODE);
//code
String code = request.getParameter(OAuth2ParameterNames.CODE);
if (StringUtils.isEmpty(code)) {
@ -145,7 +145,7 @@ public class WeChatWorkScanCodeLoginAuthenticationFilter extends
String userId = StringUtils.defaultString(result.getString("UserId"),
result.getString("OpenId"));
IdpUser idpUser = IdpUser.builder().openId(userId).build();
return attemptAuthentication(request, response, WECHATWORK_SCAN_CODE, providerId, idpUser);
return attemptAuthentication(request, response, WECHAT_WORK_QR, providerId, idpUser);
}
/**
@ -186,7 +186,7 @@ public class WeChatWorkScanCodeLoginAuthenticationFilter extends
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl()
+ WECHATWORK_SCAN_CODE.getLoginPathPrefix() + "/" + providerId;
+ WECHAT_WORK_QR.getLoginPathPrefix() + "/" + providerId;
return url.replaceAll("(?<!(http:|https:))/+", "/");
}

View File

@ -30,19 +30,19 @@ public final class AccountConstants {
/**
* API
*/
public static final String USER_PATH = API_PATH + "/user";
public final static String USER_PATH = API_PATH + "/user";
/**
* API
*/
public static final String ORGANIZATION_PATH = API_PATH + "/organization";
public final static String ORGANIZATION_PATH = API_PATH + "/organization";
/**
* API
*/
public static final String USER_GROUP_PATH = API_PATH + "/user_group";
public final static String USER_GROUP_PATH = API_PATH + "/user_group";
/**
* API
*/
public static final String IDENTITY_SOURCE_PATH = API_PATH + "/identity_source";
public final static String IDENTITY_SOURCE_PATH = API_PATH + "/identity_source";
/**
*

View File

@ -30,7 +30,7 @@ public final class AnalysisConstants {
/**
* API
*/
public static final String ANALYSIS_PATH = API_PATH + "/analysis";
public final static String ANALYSIS_PATH = API_PATH + "/analysis";
/**
*

View File

@ -31,7 +31,7 @@ public final class AppConstants {
/**
* API
*/
public static final String APP_PATH = API_PATH + "/app";
public final static String APP_PATH = API_PATH + "/app";
/**
*

View File

@ -29,7 +29,7 @@ public final class AuditConstants {
/**
* API
*/
public static final String AUDIT_PATH = API_PATH + "/audit";
public final static String AUDIT_PATH = API_PATH + "/audit";
/**
*

View File

@ -26,7 +26,7 @@ import cn.topiam.employee.support.constant.EiamConstants;
* Created by support@topiam.cn on 2020/7/26 19:07
*/
public final class AuthenticationConstants {
public static final String AUTHENTICATION_PATH = EiamConstants.API_PATH
public final static String AUTHENTICATION_PATH = EiamConstants.API_PATH
+ "/authentication";
/**

View File

@ -30,28 +30,19 @@ public final class AuthorizeConstants {
* LOGIN
*/
public static final String LOGIN_PATH = EiamConstants.API_PATH + "/login";
public static final String AUTHORIZE_PATH = EiamConstants.API_PATH + "/authorize";
public final static String AUTHORIZE_PATH = EiamConstants.API_PATH + "/authorize";
public static final String AUTHORIZATION_REQUEST_URI = EiamConstants.API_PATH
+ "/authorization";
/**
* form
*/
public static final String FORM_LOGIN = LOGIN_PATH;
/**
* sms login
*/
public static final String SMS_LOGIN = LOGIN_PATH + "/sms";
/**
* maf
*/
public static final String MFA_VALIDATE = LOGIN_PATH + "/mfa/validate";
/**
* mfa
*/
public static final String LOGIN_MFA_FACTORS = LOGIN_PATH + "/mfa/factors";
/**
*
*/

View File

@ -32,10 +32,12 @@ public final class ConfigBeanNameConstants {
*
*/
public static final String DEFAULT_SECURITY_FILTER_CHAIN = "defaultSecurityFilterChain";
public static final String SOCIAL_SECURITY_FILTER_CHAIN = "socialSecurityFilterChain";
public static final String IDP_SECURITY_FILTER_CHAIN = "idpSecurityFilterChain";
public static final String SAML2_PROTOCOL_SECURITY_FILTER_CHAIN = "saml2ProtocolSecurityFilterChain";
public static final String OIDC_PROTOCOL_SECURITY_FILTER_CHAIN = "oidcProtocolSecurityFilterChain";
public static final String FORM_PROTOCOL_SECURITY_FILTER_CHAIN = "formProtocolSecurityFilterChain";
public static final String CAS_PROTOCOL_SECURITY_FILTER_CHAIN = "casProtocolSecurityFilterChain";
public static final String TSA_PROTOCOL_SECURITY_FILTER_CHAIN = "tsaProtocolSecurityFilterChain";
/**
*

View File

@ -64,6 +64,16 @@ public final class ProtocolConstants {
*/
public static final String APP_CERT_CACHE_NAME = APP_CACHE_NAME_PREFIX + "cert";
/**
* FORM
*/
public static final String FORM_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "form";
/**
* TSA
*/
public static final String TSA_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "tsa";
/**
* OIDC Endpoint config
*/
@ -73,21 +83,21 @@ public final class ProtocolConstants {
/**
* OIDC BASE
*/
public static final String OIDC_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/" + APP_CODE_VARIABLE;
public final static String OIDC_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/" + APP_CODE_VARIABLE;
public static final String OIDC_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH + "/oidc";
public final static String OIDC_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oidc";
public static final String OAUTH2_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH + "/oauth2";
public final static String OAUTH2_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oauth2";
/**
* OpenID Provider metadata.
*/
public static final String WELL_KNOWN_OPENID_CONFIGURATION = OIDC_AUTHORIZE_PATH + OPENID_PROVIDER_WELL_KNOWN_PATH;
public static final String WELL_KNOWN_OPENID_CONFIGURATION = OIDC_AUTHORIZE_PATH +OPENID_PROVIDER_WELL_KNOWN_PATH;
/**
* Jwk Set Endpoint
*/
public static final String JWK_SET_ENDPOINT = OIDC_AUTHORIZE_PATH + "/jwks";
public static final String JWK_SET_ENDPOINT = OIDC_AUTHORIZE_PATH + "/jwks";
/**
* OIDC Client Registration Endpoint
@ -97,27 +107,32 @@ public final class ProtocolConstants {
/**
* Authorization Endpoint
*/
public static final String AUTHORIZATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/auth";
public static final String AUTHORIZATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/auth";
/**
* Authorization Consent Endpoint
*/
public static final String AUTHORIZATION_CONSENT_ENDPOINT = AUTHORIZATION_ENDPOINT+"/consent";
/**
* Token Endpoint
*/
public static final String TOKEN_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/token";
public static final String TOKEN_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/token";
/**
* Jwk Revocation Endpoint
*/
public static final String TOKEN_REVOCATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/revoke";
public static final String TOKEN_REVOCATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/revoke";
/**
* Token Introspection Endpoint
*/
public static final String TOKEN_INTROSPECTION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/introspect";
public static final String TOKEN_INTROSPECTION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/introspect";
/**
* OIDC User Info Endpoint
*/
public static final String OIDC_USER_INFO_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/userinfo";
public static final String OIDC_USER_INFO_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/userinfo";
//@formatter:on
}
@ -130,7 +145,7 @@ public final class ProtocolConstants {
/**
* SAML2
*/
public static final String SAML2_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/saml2/"
public final static String SAML2_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/saml2/"
+ APP_CODE_VARIABLE;
/**
@ -149,27 +164,74 @@ public final class ProtocolConstants {
public static final String SAML_SSO_PATH = SAML2_AUTHORIZE_BASE_PATH + "/sso";
}
/**
* Form Endpoint config
*/
@Data
public static class FormEndpointConstants {
/**
* FORM
*/
public final static String FORM_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/form/"
+ APP_CODE_VARIABLE;
/**
* FORM_SSO
*/
public static final String FORM_SSO_PATH = FORM_AUTHORIZE_BASE_PATH + "/sso";
/**
* FORM IDP SSO
*/
public static final String IDP_FORM_SSO_INITIATOR = FORM_AUTHORIZE_BASE_PATH
+ "/initiator";
}
@Data
public static class CasEndpointConstants {
/**
* cas
*/
public static final String CAS_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/cas/"
public final static String CAS_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/cas/"
+ APP_CODE_VARIABLE;
/**
* cas
*/
public static final String CAS_LOGIN_PATH = CAS_AUTHORIZE_BASE_PATH + "/login";
/*
public final static String CAS_LOGIN_PATH = CAS_AUTHORIZE_BASE_PATH + "/login";
/**
* cas
*/
public final static String CAS_LOGOUT_PATH = CAS_AUTHORIZE_BASE_PATH + "/logout";
/**
* cas ticket
*/
public static final String CAS_VALIDATE_V1_PATH = CAS_AUTHORIZE_BASE_PATH + "/validate";
public final static String CAS_VALIDATE_V1_PATH = CAS_AUTHORIZE_BASE_PATH + "/validate";
public static final String CAS_VALIDATE_V2_PATH = CAS_AUTHORIZE_BASE_PATH
public final static String CAS_VALIDATE_V2_PATH = CAS_AUTHORIZE_BASE_PATH
+ "/serviceValidate";
public static final String CAS_VALIDATE_V3_PATH = CAS_AUTHORIZE_BASE_PATH
public final static String CAS_VALIDATE_V3_PATH = CAS_AUTHORIZE_BASE_PATH
+ "/p3/serviceValidate";
}
/**
* TSA Endpoint config
*/
@Data
public static class TsaEndpointConstants {
/**
* TSA
*/
public final static String TSA_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/tsa/"
+ APP_CODE_VARIABLE;
/**
* TSA_SSO
*/
public static final String TSA_SSO_PATH = TSA_AUTHORIZE_BASE_PATH + "/sso";
}
}

View File

@ -26,7 +26,7 @@ import cn.topiam.employee.support.constant.EiamConstants;
* Created by support@topiam.cn on 2022/6/8 20:12
*/
public final class SessionConstants {
public static final String SESSION_PATH = EiamConstants.API_PATH + "/session";
public final static String SESSION_PATH = EiamConstants.API_PATH + "/session";
/**
* CURRENT_USER

View File

@ -31,7 +31,7 @@ public final class SettingConstants {
/**
* API
*/
public static final String SETTING_PATH = EiamConstants.API_PATH + "/setting";
public final static String SETTING_PATH = EiamConstants.API_PATH + "/setting";
/**
*
@ -48,4 +48,8 @@ public final class SettingConstants {
*/
public static final String ADMIN_CACHE_NAME = "admin";
/**
* AES
*/
public static final String AES_SECRET = "security.aes_secret";
}

View File

@ -29,7 +29,7 @@ public final class StorageConstants {
/**
* API
*/
public static final String STORAGE_PATH = EiamConstants.API_PATH + "/storage";
public final static String STORAGE_PATH = EiamConstants.API_PATH + "/storage";
/**
*

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.security.captcha;
package cn.topiam.employee.common.context;

View File

@ -0,0 +1,40 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.fasterxml.jackson.annotation.JacksonAnnotation;
/**
* Encrypt
*
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
@JacksonAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface Encrypt {
Type serializer() default Type.ENCRYPT;
Type deserializer() default Type.DECRYPT;
}

View File

@ -0,0 +1,68 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import java.util.Objects;
import org.springframework.util.Assert;
import cn.topiam.employee.common.entity.setting.SettingEntity;
import cn.topiam.employee.common.repository.setting.SettingRepository;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import cn.topiam.employee.support.util.AesUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import static cn.topiam.employee.common.constants.SettingConstants.AES_SECRET;
/**
* EncryptContextHelp
*
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EncryptContextHelp {
private static final AesUtils AES_UTILS = new AesUtils(getAesSecret());
public static String encrypt(String content) {
return AES_UTILS.encrypt(content);
}
public static String decrypt(String content) {
if (Objects.isNull(content)) {
return null;
}
return AES_UTILS.decrypt(content);
}
/**
* AES
*
* @return {@link String}
*/
public static String getAesSecret() {
SettingEntity setting = getSettingRepository().findByName(AES_SECRET);
Assert.notNull(setting, "aes secret must not be null");
return setting.getValue();
}
private static SettingRepository getSettingRepository() {
return ApplicationContextHelp.getBean(SettingRepository.class);
}
}

View File

@ -0,0 +1,61 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public class EncryptedDeserializerModifier extends BeanDeserializerModifier {
private final Type type;
public EncryptedDeserializerModifier() {
this.type = null;
}
public EncryptedDeserializerModifier(Type type) {
this.type = type;
}
@Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config,
BeanDescription beanDesc,
BeanDeserializerBuilder builder) {
var properties = builder.getProperties();
while (properties.hasNext()) {
var property = properties.next();
Encrypt annotation = property.getAnnotation(Encrypt.class);
if (annotation != null) {
Type deserializer = type;
if (type == null) {
deserializer = annotation.deserializer();
}
builder.addOrReplaceProperty(
property.withValueDeserializer(new EncryptedJsonDeserializer(deserializer)),
true);
}
}
return builder;
}
}

View File

@ -0,0 +1,54 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public class EncryptedJsonDeserializer extends JsonDeserializer<Object> {
private final Type deserializerType;
public EncryptedJsonDeserializer(Type deserializer) {
this.deserializerType = deserializer;
}
@Override
public Object deserialize(final JsonParser parser,
final DeserializationContext context) throws IOException {
String value = parser.getValueAsString();
if (StringUtils.isBlank(value)) {
return null;
}
if (Type.ENCRYPT == deserializerType) {
return EncryptContextHelp.encrypt(value);
} else if (Type.DECRYPT == deserializerType) {
return EncryptContextHelp.decrypt(value);
}
return value;
}
}

View File

@ -0,0 +1,108 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import java.io.IOException;
import java.io.StringWriter;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public class EncryptedJsonSerializer extends JsonSerializer<Object> {
/**
*
*/
private final JsonSerializer<Object> serializer;
private final Type serializerType;
public EncryptedJsonSerializer() {
this.serializer = null;
this.serializerType = null;
}
public EncryptedJsonSerializer(JsonSerializer<Object> serializer, Type type) {
this.serializer = serializer;
this.serializerType = type;
}
@Override
public void serialize(Object obj, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
StringWriter stringWriter = new StringWriter();
ObjectCodec objectCodec = jsonGenerator.getCodec();
JsonGenerator nestedGenerator = null;
//空对象或空字符串不处理。
if (obj == null || StringUtils.isEmpty(String.valueOf(obj))) {
if (serializer == null) {
serializerProvider.defaultSerializeValue(obj, jsonGenerator);
} else {
serializer.serialize(obj, jsonGenerator, serializerProvider);
}
return;
}
/*
JsonGeneratorobj
*/
if (objectCodec instanceof ObjectMapper) {
nestedGenerator = objectCodec.getFactory().createGenerator(stringWriter);
}
if (nestedGenerator == null) {
throw new NullPointerException("nestedGenerator == null");
}
/*
JsonGenerator
*/
if (serializer == null) {
serializerProvider.defaultSerializeValue(obj, nestedGenerator);
} else {
serializer.serialize(obj, nestedGenerator, serializerProvider);
}
nestedGenerator.close();
/*
JsonGenerator
*/
String value = stringWriter.getBuffer().toString();
try {
String newValue = value.substring(1, value.length() - 1);
if (StringUtils.isNotEmpty(newValue)) {
if (Type.ENCRYPT == serializerType) {
newValue = EncryptContextHelp.encrypt(newValue);
} else if (Type.DECRYPT == serializerType) {
newValue = EncryptContextHelp.decrypt(value);
}
}
jsonGenerator.writeString(newValue);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

View File

@ -0,0 +1,71 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public class EncryptedSerializerModifier extends BeanSerializerModifier {
private final Type type;
public EncryptedSerializerModifier() {
this.type = null;
}
public EncryptedSerializerModifier(Type type) {
this.type = type;
}
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
/*
beanPropertiesEncrypt.class
*/
List<BeanPropertyWriter> newWriter = new ArrayList<>();
for (BeanPropertyWriter writer : beanProperties) {
Encrypt annotation = writer.getAnnotation(Encrypt.class);
if (null == annotation) {
newWriter.add(writer);
} else {
Type deserializer = type;
if (type == null) {
deserializer = annotation.deserializer();
}
JsonSerializer<Object> serializer = new EncryptedJsonSerializer(
writer.getSerializer(), deserializer);
writer.assignSerializer(serializer);
newWriter.add(writer);
}
}
return newWriter;
}
}

View File

@ -0,0 +1,75 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public class EncryptionModule extends SimpleModule {
private final Type serializer;
private final Type deserializer;
public EncryptionModule() {
this.serializer = null;
this.deserializer = null;
}
public EncryptionModule(Type serializer, Type deserializer) {
this.serializer = serializer;
this.deserializer = deserializer;
}
@Override
public void setupModule(SetupContext setupContext) {
setupContext.addBeanSerializerModifier(new EncryptedSerializerModifier(serializer));
setupContext.addBeanDeserializerModifier(new EncryptedDeserializerModifier(deserializer));
}
public static ObjectMapper serializerEncrypt() {
return createMapper(Type.ENCRYPT, Type.NONE);
}
public static ObjectMapper deserializerEncrypt() {
return createMapper(Type.NONE, Type.ENCRYPT);
}
public static ObjectMapper serializerDecrypt() {
return createMapper(Type.DECRYPT, Type.NONE);
}
public static ObjectMapper deserializerDecrypt() {
return createMapper(Type.NONE, Type.DECRYPT);
}
public static ObjectMapper createMapper(Type serializer, Type deserializer) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.registerModule(new EncryptionModule(serializer, deserializer));
return objectMapper;
}
}

View File

@ -0,0 +1,38 @@
/*
* eiam-common - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.crypto;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/12/22 21:53
*/
public enum Type {
/**
* Encrypt
*/
ENCRYPT,
/**
* Decrypt
*/
DECRYPT,
/**
* None
*/
NONE
}

View File

@ -25,14 +25,19 @@ import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.Where;
import cn.topiam.employee.common.enums.DataOrigin;
import cn.topiam.employee.common.enums.OrganizationType;
import cn.topiam.employee.support.repository.domain.BaseEntity;
import cn.topiam.employee.support.repository.domain.LogicDeleteEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import static cn.topiam.employee.support.repository.domain.LogicDeleteEntity.SOFT_DELETE_SET;
import static cn.topiam.employee.support.repository.domain.LogicDeleteEntity.SOFT_DELETE_WHERE;
/**
* <p>
@ -46,8 +51,11 @@ import lombok.ToString;
@Setter
@ToString
@Entity
@Table(name = "`organization`")
public class OrganizationEntity extends BaseEntity<String> {
@Table(name = "organization")
@SQLDelete(sql = "update organization set " + SOFT_DELETE_SET + " where id_ = ?")
@SQLDeleteAll(sql = "update organization set " + SOFT_DELETE_SET + " where id_ = ?")
@Where(clause = SOFT_DELETE_WHERE)
public class OrganizationEntity extends LogicDeleteEntity<String> {
@Serial
private static final long serialVersionUID = 8143944323232082295L;

Some files were not shown because too many files have changed in this diff Show More