mirror of https://gitee.com/topiam/eiam
♻️ 重构代码
parent
1a1134b79d
commit
6afe10a1bd
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -52,13 +52,20 @@ public class AppCasStandardConfigGetResult {
|
|||
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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -55,9 +55,15 @@ public class AppCasStandardSaveConfigParam implements Serializable {
|
|||
@Schema(description = "SSO 发起登录URL")
|
||||
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;
|
||||
}
|
|
@ -44,6 +44,7 @@ public class ApplicationServiceLoader implements ApplicationContextAware {
|
|||
* 用于保存接口实现类名及对应的类
|
||||
*/
|
||||
private Map<String, ApplicationService> loadMap = new HashMap<>(16);
|
||||
private ApplicationContext applicationContext;
|
||||
/**
|
||||
* key: code,value:templateImpl
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* entity转config
|
||||
*
|
||||
* @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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
@ -131,7 +131,7 @@ public class AppOidcStandardConfigGetResult implements Serializable {
|
|||
* 协议端点域
|
||||
*/
|
||||
@Parameter(description = "协议端点域")
|
||||
private ProtocolEndpoint protocolEndpoint;
|
||||
private AppOidcProtocolEndpoint protocolEndpoint;
|
||||
|
||||
/**
|
||||
* Access Token 格式
|
||||
|
@ -145,58 +145,4 @@ public class AppOidcStandardConfigGetResult implements Serializable {
|
|||
@Parameter(description = "是否重用刷新令牌")
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 应用接口
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 模版配置
|
|
@ -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;
|
|
@ -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();
|
||||
//元数据端点
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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("未知认证对象");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:))/+", "/");
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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:))/+", "/");
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 组名称
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 组名称
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 组名称
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 组名称
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 登录配置
|
||||
*/
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 默认密码策略管理器
|
||||
|
|
|
@ -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,11 +83,11 @@ 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.
|
||||
|
@ -99,6 +109,11 @@ public final class ProtocolConstants {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
* 文件存储
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
/*
|
||||
生成一个新的JsonGenerator,用于将obj写入。
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
/*
|
||||
遍历beanProperties处理Encrypt.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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -24,13 +24,18 @@ 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.support.repository.domain.BaseEntity;
|
||||
import cn.topiam.employee.support.repository.domain.LogicDeleteEntity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import static cn.topiam.employee.support.repository.domain.LogicDeleteEntity.SOFT_DELETE_SET;
|
||||
import static cn.topiam.employee.support.repository.domain.LogicDeleteEntity.SOFT_DELETE_WHERE;
|
||||
|
||||
/**
|
||||
* 组织机构成员
|
||||
|
@ -43,8 +48,11 @@ import lombok.experimental.Accessors;
|
|||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Entity
|
||||
@Table(name = "`organization_member`")
|
||||
public class OrganizationMemberEntity extends BaseEntity<Long> {
|
||||
@Table(name = "organization_member")
|
||||
@SQLDelete(sql = "update organization_member set " + SOFT_DELETE_SET + " where id_ = ?")
|
||||
@SQLDeleteAll(sql = "update organization_member set " + SOFT_DELETE_SET + " where id_ = ?")
|
||||
@Where(clause = SOFT_DELETE_WHERE)
|
||||
public class OrganizationMemberEntity extends LogicDeleteEntity<Long> {
|
||||
/**
|
||||
* 组织机构ID
|
||||
*/
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue