Merge branch 'develop' of https://gitee.com/hbnuosc/eiam into develop

pull/16/MERGE^2
极客开源 2023-02-08 21:20:43 +08:00 committed by chenwen@trusfort.com
commit cf7ad75926
455 changed files with 11605 additions and 7919 deletions

View File

@ -195,7 +195,7 @@ application.yml文件依次修改以下配置
## 加入社群
![](https://user-images.githubusercontent.com/30397655/216215736-815861e7-8890-4c62-a496-e69c9c6ee216.jpg)
![](https://user-images.githubusercontent.com/30397655/217441678-f6499558-77d6-422d-92a4-13a439c0faa6.jpg)
## 参与贡献
@ -228,4 +228,13 @@ application.yml文件依次修改以下配置
- console-public-base-url (后台前端服务域名可配置IP+端口)
- portal-public-base-url (门户前台服务域名可配置IP+端口)
- openapi-public-base-url (后台服务域名可配置IP+端口)
- synchronizer-public-base-url (同步认证源服务域名可配置IP+端口)
- synchronizer-public-base-url (同步认证源服务域名可配置IP+端口)
2、提示 cn.topiam.employee.common.entity.* 相关包缺失
因项目用到了 `QueryDSL` 查询框架,需要进行编译。
解决方案:
- 使用命令 `mvn compile`之后自动生成代码对应的代码目标在target/generated-sources目录下。
- 通过 IDEA 选中其目录Mark Directory as -> generated sources root 。
- 通过 IDEA 工具类中File -> Invalidate Caches 清理缓存(可选操作)

62
docker-compose.yml Normal file
View File

@ -0,0 +1,62 @@
version: '3'
services:
eiam-console:
build:
context: ./eiam-console/
dockerfile: Dockerfile
environment:
MYSQL_HOST: 192.168.56.107
MYSQL_USER: root
MYSQL_PASSWORD: admin
ES_HOST: 192.168.56.107
REDIS_HOST: 192.168.56.107
REDIS_PASSWORD: 12345678
ports:
- 1898:1898
image: eiam-console
restart: always
eiam-openapi:
build:
context: ./eiam-openapi/
dockerfile: Dockerfile
environment:
MYSQL_HOST: 192.168.56.107
MYSQL_USER: root
MYSQL_PASSWORD: admin
ES_HOST: 192.168.56.107
REDIS_HOST: 192.168.56.107
REDIS_PASSWORD: 12345678
image: eiam-openapi
restart: always
ports:
- 1988:1988
eiam-portal:
build:
context: ./eiam-portal/
dockerfile: Dockerfile
environment:
MYSQL_HOST: 192.168.56.107
MYSQL_USER: root
MYSQL_PASSWORD: admin
ES_HOST: 192.168.56.107
REDIS_HOST: 192.168.56.107
REDIS_PASSWORD: 12345678
image: eiam-portal
restart: always
ports:
- 1989:1989
eiam-synchronizer:
build:
context: ./eiam-synchronizer/
dockerfile: Dockerfile
environment:
MYSQL_HOST: 192.168.56.107
MYSQL_USER: root
MYSQL_PASSWORD: admin
ES_HOST: 192.168.56.107
REDIS_HOST: 192.168.56.107
REDIS_PASSWORD: 12345678
image: eiam-synchronizer
restart: always
ports:
- 1986:1986

View File

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

View File

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

View File

@ -28,26 +28,19 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import cn.topiam.employee.application.cas.model.AppCasStandardConfigGetResult;
import cn.topiam.employee.application.cas.model.AppCasStandardSaveConfigParam;
import cn.topiam.employee.application.cas.converter.AppCasStandardConfigConverter;
import cn.topiam.employee.application.cas.pojo.AppCasStandardSaveConfigParam;
import cn.topiam.employee.application.exception.AppNotExistException;
import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.common.entity.app.AppCasConfigEntity;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.po.AppCasConfigPO;
import cn.topiam.employee.common.enums.app.AppProtocol;
import cn.topiam.employee.common.enums.app.AppType;
import cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import cn.topiam.employee.common.enums.app.*;
import cn.topiam.employee.common.repository.app.*;
import cn.topiam.employee.core.context.ServerContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.validation.ValidationHelp;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
/**
* Cas
*
@ -56,23 +49,7 @@ import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VAR
*/
@Component
public class CasStandardApplicationServiceImpl extends AbstractCasApplicationService {
private final Logger logger = LoggerFactory
.getLogger(CasStandardApplicationServiceImpl.class);
/**
* AppCasConfigRepository
*/
protected final AppCasConfigRepository appCasConfigRepository;
public CasStandardApplicationServiceImpl(AppCertRepository appCertRepository,
AppAccountRepository appAccountRepository,
AppAccessPolicyRepository appAccessPolicyRepository,
AppRepository appRepository,
AppCasConfigRepository appCasConfigRepository) {
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository,
appCasConfigRepository);
this.appCasConfigRepository = appCasConfigRepository;
}
private final Logger logger = LoggerFactory.getLogger(CasStandardApplicationServiceImpl.class);
/**
*
@ -119,7 +96,9 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
throw new AppNotExistException();
}
AppCasConfigEntity entity = cas.get();
entity.setSpCallbackUrl(model.getSpCallbackUrl());
entity.setClientServiceUrl(model.getClientServiceUrl());
entity.setUserIdentityType(model.getUserIdentityType());
entity.setServiceTicketExpireTime(model.getServiceTicketExpireTime());
appCasConfigRepository.save(entity);
}
@ -133,18 +112,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 +204,36 @@ 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);
casEntity.setServiceTicketExpireTime(30);
appCasConfigRepository.save(casEntity);
return appEntity.getId().toString();
}
private final AppCasStandardConfigConverter casStandardConfigConverter;
/**
* AppCasConfigRepository
*/
protected final AppCasConfigRepository appCasConfigRepository;
public CasStandardApplicationServiceImpl(AppCertRepository appCertRepository,
AppAccountRepository appAccountRepository,
AppAccessPolicyRepository appAccessPolicyRepository,
AppRepository appRepository,
AppCasConfigRepository appCasConfigRepository,
AppCasStandardConfigConverter casStandardConfigConverter) {
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository,
appCasConfigRepository);
this.appCasConfigRepository = appCasConfigRepository;
this.casStandardConfigConverter = casStandardConfigConverter;
}
}

View File

@ -19,6 +19,13 @@ package cn.topiam.employee.application.cas.converter;
import org.mapstruct.Mapper;
import cn.topiam.employee.application.cas.pojo.AppCasProtocolEndpoint;
import cn.topiam.employee.application.cas.pojo.AppCasStandardConfigGetResult;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.common.entity.app.po.AppCasConfigPO;
import cn.topiam.employee.core.context.ServerContextHelp;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
/**
*
*
@ -27,4 +34,35 @@ 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.setAppId(String.valueOf(po.getAppId()));
result.setInitLoginType(po.getInitLoginType());
result.setInitLoginUrl(po.getInitLoginUrl());
result.setClientServiceUrl(po.getClientServiceUrl());
result.setUserIdentityType(po.getUserIdentityType());
result.setServiceTicketExpireTime(po.getServiceTicketExpireTime());
//封装端点信息
//@formatter:off
AppCasProtocolEndpoint protocolEndpoint = new AppCasProtocolEndpoint();
String baseUrl = ServerContextHelp.getPortalPublicBaseUrl();
protocolEndpoint.setCasServerUrlPrefix(baseUrl+ProtocolConstants.CasEndpointConstants.CAS_AUTHORIZE_BASE_PATH.replace(APP_CODE_VARIABLE, po.getAppCode()));
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);
//@formatter:on
return result;
}
}

View File

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

View File

@ -0,0 +1,73 @@
/*
* 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
*
*
* @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 URL
*/
@Schema(description = "CAS URL前缀")
private String casServerUrlPrefix;
/**
* CAS SSO
*/
@Schema(description = "CAS SSO 端点")
private String casSsoEndpoint;
/**
* CAS SLO
*/
@Schema(description = "CAS SLO 端点")
private String casSloEndpoint;
/**
* CAS
*/
@Schema(description = "CAS 校验端点")
private String casValidateEndpoint;
/**
* CAS v2
*/
@Schema(description = "CAS V2 校验端点")
private String casValidateV2Endpoint;
/**
* CAS v3
*/
@Schema(description = "CAS V3 校验端点")
private String casValidateV3Endpoint;
}

View File

@ -0,0 +1,81 @@
/*
* 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 cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.CasUserIdentityType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/1/2 22:23
*/
@Data
@Schema(description = "CAS 配置返回结果")
public class AppCasStandardConfigGetResult {
/**
* 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(name = "客户端服务URL")
private String clientServiceUrl;
/**
*
*/
@Schema(name = "用户身份类型标识")
private CasUserIdentityType userIdentityType;
/**
* serviceTicket
*/
@Schema(name = "serviceTicket 过期时间(秒)")
private Integer serviceTicketExpireTime;
/**
* CAS
*/
@Schema(name = "CAS 协议端点")
private AppCasProtocolEndpoint protocolEndpoint;
}

View File

@ -15,19 +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 cn.topiam.employee.common.enums.app.AuthorizationType;
import cn.topiam.employee.common.enums.app.CasUserIdentityType;
import cn.topiam.employee.common.enums.app.InitLoginType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import cn.topiam.employee.common.enums.app.AuthorizationType;
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;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/1/2 22:27
@ -35,29 +33,41 @@ import io.swagger.v3.oas.annotations.media.Schema;
@Data
public class AppCasStandardSaveConfigParam implements Serializable {
@Serial
private static final long serialVersionUID = 1881187724713984421L;
private static final long serialVersionUID = 1881187724713984421L;
/**
* ID
*/
@Schema(description = "授权类型")
private AuthorizationType authorizationType;
private AuthorizationType authorizationType;
/**
* SSO
*/
@Schema(description = "SSO 发起登录类型")
private InitLoginType initLoginType;
private InitLoginType initLoginType;
/**
* SSO URL
*/
@Schema(description = "SSO 发起登录URL")
private String initLoginUrl;
private String initLoginUrl;
/**
* SP
* URL
*/
@Parameter(name = "单点登录 sp Callback Url")
private String spCallbackUrl;
@Schema(name = "客户端服务URL")
private String clientServiceUrl;
/**
*
*/
@Schema(name = "用户身份类型标识")
private CasUserIdentityType userIdentityType;
/**
* serviceTicket
*/
@Schema(name = "serviceTicket 过期时间(秒)")
private Integer serviceTicketExpireTime;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - Employee Identity and Access Management Program
* 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
@ -15,38 +15,32 @@
* 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.portal.pojo.result;
package cn.topiam.employee.application.form.pojo;
import java.io.Serial;
import java.io.Serializable;
import cn.topiam.employee.common.enums.MfaFactor;
import lombok.Builder;
import lombok.Data;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* Mfa
*
* @author TopIAM
* Created by support@topiam.cn on 2022/8/13 21:29
*/
@Builder
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/6/4 23:37
*/
@Data
public class LoginMfaFactorResult implements Serializable {
@Schema(description = "协议端点")
public class AppFormProtocolEndpoint implements Serializable {
@Serial
private static final long serialVersionUID = 7255002979319970337L;
private static final long serialVersionUID = -2261602995152894964L;
/**
* provider
* IDP SSO
*/
private MfaFactor factor;
/**
*
*/
private Boolean usable;
/**
*
*/
private String target;
@Parameter(description = "IDP SSO 端点")
private String idpSsoEndpoint;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.security.core.Authentication;
import org.springframework.util.CollectionUtils;
import com.alibaba.ttl.TransmittableThreadLocal;
@ -41,6 +42,11 @@ public class AuditContext {
*/
private static final TransmittableThreadLocal<String> CONTENT = new TransmittableThreadLocal<>();
/**
* Authentication
*/
private static final TransmittableThreadLocal<Authentication> AUTHENTICATION = new TransmittableThreadLocal<>();
/**
*
*/
@ -122,6 +128,19 @@ public class AuditContext {
ADDITIONAL_DATA.set(value);
}
/**
* Get Authentication
*
* @return {@link Authentication}
*/
public static Authentication getAuthorization() {
return AUTHENTICATION.get();
}
public static void setAuthorization(Authentication authorization) {
AUTHENTICATION.set(authorization);
}
/**
* Get Target
*
@ -156,6 +175,13 @@ public class AuditContext {
TARGET_LIST.remove();
}
/**
* Remove Authentication
*/
public static void removeAuthentication() {
AUTHENTICATION.remove();
}
/**
* remove
*/
@ -182,5 +208,7 @@ public class AuditContext {
removeAdditionalData();
removeContent();
removeTarget();
removeAuthentication();
}
}

View File

@ -40,6 +40,8 @@ public class Actor implements Serializable {
public static final String ACTOR_ID = "actor.id";
public static final String ACTOR_TYPE = "actor.type";
public static final String ACTOR_AUTH_TYPE = "actor.auth_type.keyword";
@Serial
private static final long serialVersionUID = -1144169992714000310L;
/**
@ -54,4 +56,10 @@ public class Actor implements Serializable {
@Field(type = FieldType.Keyword, name = "type")
private UserType type;
/**
*
*/
@Field(type = FieldType.Keyword, name = "auth_type")
private String authType;
}

View File

@ -25,18 +25,23 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Where;
import cn.topiam.employee.audit.enums.EventStatus;
import cn.topiam.employee.audit.enums.EventType;
import cn.topiam.employee.common.enums.UserType;
import cn.topiam.employee.support.repository.domain.BaseEntity;
import cn.topiam.employee.support.repository.domain.LogicDeleteEntity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
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;
/**
*
@ -51,7 +56,10 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@Entity
@Table(name = "audit")
public class AuditEntity extends BaseEntity<Long> {
@SQLDelete(sql = "update audit set " + SOFT_DELETE_SET + " where id_ = ?")
@SQLDeleteAll(sql = "update audit set " + SOFT_DELETE_SET + " where id_ = ?")
@Where(clause = SOFT_DELETE_WHERE)
public class AuditEntity extends LogicDeleteEntity<Long> {
@Serial
private static final long serialVersionUID = -3119319193111206582L;
@ -136,4 +144,10 @@ public class AuditEntity extends BaseEntity<Long> {
*/
@Column(name = "actor_type")
private UserType actorType;
/**
*
*/
@Column(name = "actor_auth_type")
private String actorAuthType;
}

View File

@ -64,7 +64,7 @@ public class Event implements Serializable {
/**
*
*/
@Field(type = FieldType.Text, name = "content")
@Field(type = FieldType.Object, name = "content")
private String content;
/**

View File

@ -41,7 +41,9 @@ import lombok.Data;
public class GeoLocation implements Serializable {
@Serial
private static final long serialVersionUID = -1144169992714000310L;
private static final long serialVersionUID = -1144169992714000310L;
public static final String GEO_LOCATION_PROVINCE_CODE = "geo_location.province_code.keyword";
/**
* IP

View File

@ -48,6 +48,12 @@ public class Target implements Serializable {
*/
@Field(type = FieldType.Keyword, name = "id")
private String id;
/**
*
*/
@Field(type = FieldType.Keyword, name = "name")
private String name;
/**
*
*
@ -55,4 +61,9 @@ public class Target implements Serializable {
@Field(type = FieldType.Keyword, name = "type")
private TargetType type;
/**
*
*/
@Field(type = FieldType.Keyword, name = "type_name")
private String typeName;
}

View File

@ -18,7 +18,7 @@
package cn.topiam.employee.audit.event;
import java.io.Serial;
import java.util.*;
import java.util.List;
import org.springframework.context.ApplicationEvent;

View File

@ -30,7 +30,7 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.repository.*;
import cn.topiam.employee.audit.repository.AuditRepository;
import cn.topiam.employee.core.configuration.EiamSupportProperties;
import static cn.topiam.employee.common.constants.AuditConstants.getAuditIndexPrefix;
import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_FORMATTER_PATTERN;

View File

@ -35,6 +35,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Maps;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.enums.EventStatus;
@ -86,6 +87,35 @@ public class AuditEventPublish {
//@formatter:on
}
/**
*
*
* @param eventType {@link EventType}
*/
public void publish(EventType eventType, Authentication authentication, EventStatus eventStatus,
List<Target> targets, String result) {
//@formatter:off
//封装操作事件
Event event = Event.builder()
.type(eventType)
.time(Instant.now())
.result(result)
.status(eventStatus).build();
if (authentication.getPrincipal() instanceof UserDetails){
String username = ((UserDetails) authentication.getPrincipal()).getUsername();
event.setContent(username+""+event.getType().getDesc());
}
//封装地理位置
GeoLocation geoLocationModal = getGeoLocation();
//封装用户代理
UserAgent userAgent = getUserAgent();
//封装操作人
Actor actor = getActor(authentication);
//Publish AuditEvent
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, targets));
//@formatter:on
}
/**
*
*
@ -99,9 +129,12 @@ public class AuditEventPublish {
.type(eventType)
.time(Instant.now())
.status(eventStatus).build();
if (authentication.getPrincipal() instanceof UserDetails){
String username = ((UserDetails) authentication.getPrincipal()).getUsername();
event.setContent(username+""+event.getType().getDesc());
if (authentication.getPrincipal() instanceof UserDetails principal){
String username = principal.getUsername();
Map<String,String> content= Maps.newConcurrentMap();
content.put("auth_type",principal.getAuthType());
content.put("desc",username+""+event.getType().getDesc());
event.setContent(JSONObject.toJSONString(content));
}
//封装地理位置
GeoLocation geoLocationModal = getGeoLocation();
@ -209,10 +242,16 @@ public class AuditEventPublish {
//@formatter:off
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
return Actor.builder()
Object principal = authentication.getPrincipal();
Actor actor = Actor.builder()
.id(getActorId(authentication))
.type(getActorType(authentication))
.build();
if (principal instanceof UserDetails){
actor.setAuthType(((UserDetails) principal).getAuthType());
}
return actor;
//@formatter:on
}
@ -223,10 +262,15 @@ public class AuditEventPublish {
*/
private Actor getActor(Authentication authentication) {
//@formatter:off
return Actor.builder()
Actor actor = Actor.builder()
.id(getActorId(authentication))
.type(getActorType(authentication))
.build();
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails){
actor.setAuthType(((UserDetails) principal).getAuthType());
}
return actor;
//@formatter:on
}

View File

@ -21,11 +21,11 @@ import java.time.LocalDateTime;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import cn.topiam.employee.audit.entity.AuditEntity;
import cn.topiam.employee.support.repository.LogicDeleteRepository;
/**
* repository
@ -34,7 +34,7 @@ import cn.topiam.employee.audit.entity.AuditEntity;
* Created by support@topiam.cn on 2021/9/11 22:32
*/
@Repository
public interface AuditRepository extends CrudRepository<AuditEntity, Long>,
public interface AuditRepository extends LogicDeleteRepository<AuditEntity, Long>,
QuerydslPredicateExecutor<AuditEntity> {
/**

View File

@ -40,13 +40,33 @@ import com.google.common.collect.Lists;
import cn.topiam.employee.audit.controller.pojo.AuditListQuery;
import cn.topiam.employee.audit.controller.pojo.AuditListResult;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.entity.Actor;
import cn.topiam.employee.audit.entity.AuditElasticSearchEntity;
import cn.topiam.employee.audit.entity.Event;
import cn.topiam.employee.audit.entity.Target;
import cn.topiam.employee.audit.enums.EventType;
import cn.topiam.employee.audit.enums.TargetType;
import cn.topiam.employee.common.entity.account.OrganizationEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.account.UserGroupEntity;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.entity.app.AppPermissionResourceEntity;
import cn.topiam.employee.common.entity.app.AppPermissionRoleEntity;
import cn.topiam.employee.common.entity.authentication.IdentityProviderEntity;
import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity;
import cn.topiam.employee.common.entity.setting.AdministratorEntity;
import cn.topiam.employee.common.entity.setting.MailTemplateEntity;
import cn.topiam.employee.common.enums.UserType;
import cn.topiam.employee.common.repository.account.OrganizationRepository;
import cn.topiam.employee.common.repository.account.UserGroupRepository;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.app.AppPermissionResourceRepository;
import cn.topiam.employee.common.repository.app.AppPermissionRoleRepository;
import cn.topiam.employee.common.repository.app.AppRepository;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.common.repository.identitysource.IdentitySourceRepository;
import cn.topiam.employee.common.repository.setting.AdministratorRepository;
import cn.topiam.employee.common.repository.setting.MailTemplateRepository;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import cn.topiam.employee.support.repository.page.domain.Page;
import cn.topiam.employee.support.repository.page.domain.PageModel;
@ -69,7 +89,7 @@ public interface AuditDataConverter {
* searchHits
*
* @param search {@link SearchHits}
* @param page {@link PageModel}
* @param page {@link PageModel}
* @return {@link Page}
*/
default Page<AuditListResult> searchHitsConvertToAuditListResult(SearchHits<AuditElasticSearchEntity> search,
@ -94,6 +114,14 @@ public interface AuditDataConverter {
//用户类型
result.setUserType(actor.getType().getCode());
//操作对象
if (Objects.nonNull(content.getTargets())) {
for (Target target : content.getTargets()) {
if (Objects.nonNull(target.getId())) {
target.setName(getTargetName(target.getType(), target.getId()));
}
target.setTypeName(target.getType().getDesc());
}
}
result.setTargets(content.getTargets());
list.add(result);
});
@ -102,7 +130,7 @@ public interface AuditDataConverter {
result.setPagination(Page.Pagination.builder()
.total(search.getTotalHits())
.totalPages(Math.toIntExact(search.getTotalHits() / page.getPageSize()))
.current(page.getCurrent()+1)
.current(page.getCurrent() + 1)
.build());
result.setList(list);
//@formatter:on
@ -110,10 +138,9 @@ public interface AuditDataConverter {
}
/**
*
*
*
* @param actorId {@link String}
* @param actorId {@link String}
* @param actorType {@link UserType}
* @return {@link String}
*/
@ -140,7 +167,7 @@ public interface AuditDataConverter {
*
*
* @param query {@link AuditListQuery}
* @param page {@link PageModel}
* @param page {@link PageModel}
* @return {@link NativeSearchQuery}
*/
default NativeSearchQuery auditListRequestConvertToNativeSearchQuery(AuditListQuery query,
@ -201,4 +228,118 @@ public interface AuditDataConverter {
//排序
.withSorts(fieldSortBuilders).build();
}
/**
*
*
* @param targetType {@link TargetType}
* @param id {@link String}
* @return
*/
@SuppressWarnings("AlibabaMethodTooLong")
default String getTargetName(TargetType targetType, String id) {
String name = "";
if (TargetType.USER.equals(targetType) || TargetType.USER_DETAIL.equals(targetType)) {
UserRepository userRepository = ApplicationContextHelp.getBean(UserRepository.class);
Optional<UserEntity> user = userRepository.findByIdContainsDeleted(Long.valueOf(id));
if (user.isPresent()) {
name = user.get().getUsername();
}
}
if (TargetType.USER_GROUP.equals(targetType)) {
UserGroupRepository userGroupRepository = ApplicationContextHelp
.getBean(UserGroupRepository.class);
Optional<UserGroupEntity> userGroup = userGroupRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (userGroup.isPresent()) {
name = userGroup.get().getName();
}
}
if (TargetType.IDENTITY_SOURCE.equals(targetType)) {
IdentitySourceRepository identitySourceRepository = ApplicationContextHelp
.getBean(IdentitySourceRepository.class);
Optional<IdentitySourceEntity> identitySource = identitySourceRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (identitySource.isPresent()) {
name = identitySource.get().getName();
}
}
if (TargetType.ORGANIZATION.equals(targetType)) {
OrganizationRepository organizationRepository = ApplicationContextHelp
.getBean(OrganizationRepository.class);
Optional<OrganizationEntity> organizationEntity = organizationRepository
.findByIdContainsDeleted(id);
if (organizationEntity.isPresent()) {
name = organizationEntity.get().getName();
}
}
if (TargetType.APPLICATION.equals(targetType)) {
AppRepository appRepository = ApplicationContextHelp.getBean(AppRepository.class);
Optional<AppEntity> appEntity = appRepository.findByIdContainsDeleted(Long.valueOf(id));
if (appEntity.isPresent()) {
name = appEntity.get().getName();
}
}
if (TargetType.APP_PERMISSION_RESOURCE.equals(targetType)) {
AppPermissionResourceRepository appPermissionResourceRepository = ApplicationContextHelp
.getBean(AppPermissionResourceRepository.class);
Optional<AppPermissionResourceEntity> appPermissionResourceEntity = appPermissionResourceRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (appPermissionResourceEntity.isPresent()) {
name = appPermissionResourceEntity.get().getName();
}
}
if (TargetType.APPLICATION_ACCOUNT.equals(targetType)) {
if (org.apache.commons.lang3.StringUtils.isNotBlank(id)) {
name = id;
}
}
if (TargetType.APP_PERMISSION_ROLE.equals(targetType)) {
AppPermissionRoleRepository appPermissionResourceRepository = ApplicationContextHelp
.getBean(AppPermissionRoleRepository.class);
Optional<AppPermissionRoleEntity> appPermissionRoleEntity = appPermissionResourceRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (appPermissionRoleEntity.isPresent()) {
name = appPermissionRoleEntity.get().getName();
}
}
if (TargetType.ADMINISTRATOR.equals(targetType)) {
AdministratorRepository administratorRepository = ApplicationContextHelp
.getBean(AdministratorRepository.class);
Optional<AdministratorEntity> administratorEntity = administratorRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (administratorEntity.isPresent()) {
name = administratorEntity.get().getUsername();
}
}
if (TargetType.MAIL_TEMPLATE.equals(targetType)) {
MailTemplateRepository mailTemplateRepository = ApplicationContextHelp
.getBean(MailTemplateRepository.class);
Optional<MailTemplateEntity> mailTemplateEntity = mailTemplateRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (mailTemplateEntity.isPresent()) {
name = mailTemplateEntity.get().getSender();
}
}
if (TargetType.IDENTITY_PROVIDER.equals(targetType)) {
IdentityProviderRepository identityProviderRepository = ApplicationContextHelp
.getBean(IdentityProviderRepository.class);
Optional<IdentityProviderEntity> identityProviderEntity = identityProviderRepository
.findByIdContainsDeleted(Long.valueOf(id));
if (identityProviderEntity.isPresent()) {
name = identityProviderEntity.get().getName();
}
}
return name;
}
}

View File

@ -17,7 +17,10 @@
*/
package cn.topiam.employee.audit.service.impl;
import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;

View File

@ -63,6 +63,12 @@
<artifactId>eiam-authentication-wechatwork</artifactId>
<version>${project.version}</version>
</dependency>
<!-- captcha-->
<dependency>
<groupId>cn.topiam</groupId>
<artifactId>eiam-authentication-captcha</artifactId>
<version>${project.version}</version>
</dependency>
<!-- sms-->
<dependency>
<groupId>cn.topiam</groupId>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
eiam-authentication-captcha - 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>eiam-authentication</artifactId>
<groupId>cn.topiam</groupId>
<version>1.0.0-beta1</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eiam-authentication-captcha</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- common -->
<dependency>
<groupId>cn.topiam</groupId>
<artifactId>eiam-authentication-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.security.captcha;
package cn.topiam.employee.authentication.captcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.security.captcha;
package cn.topiam.employee.authentication.captcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@ -0,0 +1,18 @@
/*
* eiam-authentication-captcha - 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.captcha.configurer;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.security.captcha;
package cn.topiam.employee.authentication.captcha.filter;
import java.io.IOException;
import java.util.Objects;
@ -38,6 +38,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.authentication.captcha.CaptchaValidator;
import cn.topiam.employee.common.constants.AuthorizeConstants;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.trace.TraceUtils;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.geetest;
package cn.topiam.employee.authentication.captcha.filter;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.security.captcha.geetest;
package cn.topiam.employee.authentication.captcha.geetest;
import java.io.Serial;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.security.captcha.geetest;
package cn.topiam.employee.authentication.captcha.geetest;
import java.util.Map;
@ -33,8 +33,8 @@ import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.authentication.captcha.CaptchaValidator;
import cn.topiam.employee.common.util.RequestUtils;
import cn.topiam.employee.core.security.captcha.CaptchaValidator;
/**
*
@ -57,17 +57,17 @@ public class GeeTestCaptchaValidator implements CaptchaValidator {
*/
@Override
public boolean validate(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> getParams = RequestUtils.getParams(request);
Map<String, Object> getParams = RequestUtils.getParams(request);
// 1.初始化极验参数信息
String captchaId = config.getCaptchaId();
String captchaKey = config.getCaptchaKey();
String domain = "https://gcaptcha4.geetest.com";
// 2.获取用户验证后前端传过来的验证流水号等参数
String lotNumber = getParams.get("lot_number");
String captchaOutput = getParams.get("captcha_output");
String passToken = getParams.get("pass_token");
String genTime = getParams.get("gen_time");
String lotNumber = (String) getParams.get("lot_number");
String captchaOutput = (String) getParams.get("captcha_output");
String passToken = (String) getParams.get("pass_token");
String genTime = (String) getParams.get("gen_time");
// 3.生成签名
// 生成签名使用标准的hmac算法使用用户当前完成验证的流水号lot_number作为原始消息message使用客户验证私钥作为key

View File

@ -0,0 +1,18 @@
/*
* eiam-authentication-captcha - 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.captcha.geetest;

View File

@ -1,5 +1,5 @@
/*
* eiam-core - Employee Identity and Access Management Program
* eiam-authentication-captcha - 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.authentication.sms;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-protocol-oidc - 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,10 +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.authentication.common.constant;
/**
*
*
*
* @author TopIAM
* Created by support@topiam.cn on 2020/10/29 23:12
* Created by support@topiam.cn on 2021/12/20 23:19
*/
package cn.topiam.employee.protocol.oidc.handler;
public final class AuthenticationConstants {
/**
* ID
*/
public static final String PROVIDER_CODE = "providerId";
}

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,24 +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.portal.mfa.sms;
package cn.topiam.employee.authentication.common.exception;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.support.exception.TopIamException;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
/**
* OTP
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
* Created by support@topiam.cn on 2022/12/20 22:50
*/
public class SmsOtpProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
return true;
public class IdentityProviderNotExistException extends TopIamException {
public IdentityProviderNotExistException() {
super("idp_not_exist", "身份提供商不存在", BAD_REQUEST);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,15 +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.common.IdentityProviderType.DINGTALK_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.dingtalk.constant.DingTalkAuthenticationConstants.*;
import static cn.topiam.employee.authentication.dingtalk.filter.DingtalkScanCodeAuthorizationRequestGetFilter.PROVIDER_ID;
import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN_CODE;
/**
*
@ -80,14 +79,13 @@ import static cn.topiam.employee.common.enums.IdentityProviderType.DINGTALK_SCAN
@SuppressWarnings("DuplicatedCode")
public class DingtalkScanCodeAuthenticationFilter extends
AbstractIdpAuthenticationProcessingFilter {
public final static 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
@ -117,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)) {
@ -195,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);
}
/**
@ -234,8 +231,8 @@ public class DingtalkScanCodeAuthenticationFilter extends
private Cache<String, String> cache;
public static String getLoginUrl(String providerId) {
String url = ServerContextHelp.getPortalPublicBaseUrl()
+ DINGTALK_SCAN_CODE.getLoginPathPrefix() + "/" + providerId;
String url = ServerContextHelp.getPortalPublicBaseUrl() + DINGTALK_QR.getLoginPathPrefix()
+ "/" + providerId;
return HttpUrlUtils.format(url);
}

View File

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

View File

@ -0,0 +1,52 @@
/*
* 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;
import java.io.Serial;
import javax.validation.constraints.NotBlank;
import cn.topiam.employee.authentication.common.config.IdentityProviderConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/12/19 22:58
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class FeiShuIdpScanCodeConfig extends IdentityProviderConfig {
@Serial
private static final long serialVersionUID = -6850223527422243076L;
/**
* APP ID
*/
@NotBlank(message = "APP ID 不能为空")
private String appId;
/**
* APP Secret
*/
@NotBlank(message = "APP Secret 不能为空")
private String appSecret;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,15 +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.portal.mfa;
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.portal.handler.PortalAuthenticationHandler;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
/**
* Mfa Authentication Configurer
@ -34,7 +37,19 @@ import cn.topiam.employee.portal.handler.PortalAuthenticationHandler;
public final class MfaAuthenticationConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, MfaAuthenticationConfigurer<H>, MfaAuthenticationFilter> {
public MfaAuthenticationConfigurer() {
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);
}
/**
@ -52,8 +67,8 @@ public final class MfaAuthenticationConfigurer<H extends HttpSecurityBuilder<H>>
@Override
public void init(H http) throws Exception {
//设置登录成功失败处理器
super.successHandler(new PortalAuthenticationHandler());
super.failureHandler(new PortalAuthenticationHandler());
super.successHandler(mfaAuthenticationHandler);
super.failureHandler(mfaAuthenticationHandler);
//MFA认证
MfaAuthenticationFilter loginAuthenticationFilter = new MfaAuthenticationFilter();
this.setAuthenticationFilter(loginAuthenticationFilter);
@ -64,12 +79,20 @@ public final class MfaAuthenticationConfigurer<H extends HttpSecurityBuilder<H>>
@Override
public void configure(H http) throws Exception {
http.addFilterAfter(this.getAuthenticationFilter(),
//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 RequestMatcher getRequestMatcher() {
public static RequestMatcher getRequestMatcher() {
return MfaAuthenticationFilter.getRequestMatcher();
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,12 +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.portal.mfa;
package cn.topiam.employee.authentication.mfa;
import java.io.IOException;
import java.util.Objects;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -35,16 +33,15 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
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.MessageNoticeChannel;
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.otp.OtpContextHelp;
import cn.topiam.employee.core.security.util.UserUtils;
import cn.topiam.employee.portal.mfa.totp.TotpProviderValidator;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import static cn.topiam.employee.common.constants.AuthorizeConstants.MFA_VALIDATE;
import static cn.topiam.employee.authentication.mfa.constant.MfaAuthenticationConstants.MFA_VALIDATE;
import static cn.topiam.employee.common.enums.MfaFactor.SMS_OTP;
/**
@ -69,7 +66,7 @@ public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFil
super(MFA_LOGIN_MATCHER);
}
public static RequestMatcher getRequestMatcher() {
protected static RequestMatcher getRequestMatcher() {
return MFA_LOGIN_MATCHER;
}
@ -95,14 +92,11 @@ public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFil
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException,
ServletException {
HttpServletResponse response) throws AuthenticationException {
UserEntity user = UserUtils.getUser();
OtpContextHelp bean = ApplicationContextHelp.getBean(OtpContextHelp.class);
MfaAuthentication authentication = (MfaAuthentication) SecurityContextHolder.getContext()
.getAuthentication();
Boolean result = false;
boolean result = false;
//获取类型
MfaFactor type = MfaFactor.getType(request.getParameter(SPRING_SECURITY_FORM_TYPE_KEY));
if (Objects.isNull(type)) {
@ -114,7 +108,7 @@ public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFil
if (StringUtils.isBlank(otp)) {
throw new MfaRequiredException("OTP 参数不存在");
}
result = bean.checkOtp(type.getCode(), MessageNoticeChannel.SMS, "", otp);
result = smsOtpProviderValidator.validate(otp);
}
//Mail OPT
if (MfaFactor.EMAIL_OTP.equals(type)) {
@ -122,12 +116,12 @@ public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFil
if (StringUtils.isBlank(otp)) {
throw new MfaRequiredException("OTP 参数不存在");
}
result = bean.checkOtp(type.getCode(), MessageNoticeChannel.MAIL, "", otp);
result = emailOtpProviderValidator.validate(otp);
}
//TOTP
if (MfaFactor.APP_TOTP.equals(type)) {
long totp = Long.parseLong(request.getParameter(SPRING_SECURITY_FORM_TOTP_KEY));
result = new TotpProviderValidator().validate(String.valueOf(totp));
result = totpProviderValidator.validate(String.valueOf(totp));
}
if (!result) {
logger.error("用户ID: [{}] 用户名: [{}] {} 认证失败", type.getDesc(), user.getId(),
@ -140,4 +134,8 @@ public class MfaAuthenticationFilter extends AbstractAuthenticationProcessingFil
authentication.setValidated(true);
return authentication;
}
protected final EmailOtpProviderValidator emailOtpProviderValidator = new EmailOtpProviderValidator();
protected final SmsOtpProviderValidator smsOtpProviderValidator = new SmsOtpProviderValidator();
protected final TotpProviderValidator totpProviderValidator = new TotpProviderValidator();
}

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,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.portal.handler;
package cn.topiam.employee.authentication.mfa;
import java.io.IOException;
@ -31,11 +31,10 @@ 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.authentication.IdpAuthentication;
import cn.topiam.employee.core.security.authentication.SmsAuthentication;
import cn.topiam.employee.core.security.mfa.MfaAuthentication;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.util.HttpResponseUtils;
@ -53,14 +52,20 @@ import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX00010
* Created by support@topiam.cn on 2022/7/28 23:36
*/
@SuppressWarnings("DuplicatedCode")
public class PortalAuthenticationHandler implements AuthenticationSuccessHandler,
AuthenticationFailureHandler {
public class MfaAuthenticationHandler implements AuthenticationSuccessHandler,
AuthenticationFailureHandler {
private static final String REQUIRE_MFA = "require_mfa";
private final AuthenticationSuccessHandler successHandler = new PortalAuthenticationSuccessHandler();
private final AuthenticationFailureHandler failureHandler = new PortalAuthenticationFailureHandler();
private final AuthenticationSuccessHandler successHandler;
private final AuthenticationFailureHandler failureHandler;
private static final String REQUIRE_MFA = "require_mfa";
private static final String REQUIRE_USER_BIND = "require_user_bind";
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.
@ -90,35 +95,6 @@ public class PortalAuthenticationHandler implements AuthenticationSuccessHandler
Authentication authentication) throws IOException,
ServletException {
boolean isTextHtml = acceptIncludeTextHtml(request);
//TODO SMS 不需要双因素
if (authentication instanceof SmsAuthentication) {
successHandler.onAuthenticationSuccess(request, response, authentication);
return;
}
//TODO IDP 未关联
if (authentication instanceof IdpAuthentication
&& !((IdpAuthentication) authentication).getAssociated()) {
//Clear Authentication Attributes
clearAuthenticationAttributes(request);
if (response.isCommitted()) {
return;
}
if (!isTextHtml) {
HttpResponseUtils.flushResponseJson(response, HttpStatus.BAD_REQUEST.value(),
ApiRestResult.builder().status(REQUIRE_USER_BIND).message(REQUIRE_USER_BIND)
.build());
return;
}
//跳转登录,前端会有接口获取状态,并进行展示绑定页面
response.sendRedirect(HttpUrlUtils
.format(ServerContextHelp.getPortalPublicBaseUrl() + AuthorizeConstants.FE_LOGIN));
return;
}
//TODO IDP 不需要双因素
if (authentication instanceof IdpAuthentication) {
successHandler.onAuthenticationSuccess(request, response, authentication);
return;
}
//TODO MFA启用、但是对象非MFA说明需要MFA认证
if (isMfaEnabled() && !(authentication instanceof MfaAuthentication)) {
SecurityContextHolder.getContext()

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,9 +15,15 @@
* 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.portal.mfa.email;
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
@ -33,6 +39,9 @@ public class EmailOtpProviderValidator implements MfaProviderValidator {
*/
@Override
public boolean validate(String code) {
return true;
UserEntity user = UserUtils.getUser();
OtpContextHelp bean = ApplicationContextHelp.getBean(OtpContextHelp.class);
return bean.checkOtp(MailType.AGAIN_VERIFY.getCode(), MessageNoticeChannel.MAIL,
user.getEmail(), code);
}
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - 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,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.portal.mfa.totp;
package cn.topiam.employee.authentication.mfa.totp;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
@ -37,7 +37,9 @@ public class TotpProviderValidator implements MfaProviderValidator {
@Override
public boolean validate(String code) {
UserEntity user = UserUtils.getUser();
return new TotpAuthenticator().checkCode(user.getSharedSecret(), Long.parseLong(code),
return totpAuthenticator.checkCode(user.getSharedSecret(), Long.parseLong(code),
System.currentTimeMillis());
}
private final TotpAuthenticator totpAuthenticator = new TotpAuthenticator();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-protocol-form - Employee Identity and Access Management Program
* 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
@ -15,23 +15,29 @@
* 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.protocol.form.constant;
package cn.topiam.employee.authentication.sms.constant;
import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZE_PATH;
import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE;
import static cn.topiam.employee.common.constants.AuthorizeConstants.LOGIN_PATH;
/**
*
* Sms
*
* @author TopIAM
* Created by support@topiam.cn on 2021/12/8 21:29
* Created by support@topiam.cn on 2021/12/19 23:19
*/
public class ProtocolConstants {
public final class SmsAuthenticationConstants {
/**
* FORM IDP SSO
* sms login
*/
public static final String IDP_FORM_SSO_INITIATOR = AUTHORIZE_PATH + "/form/"
+ APP_CODE_VARIABLE + "/initiator";
public static final String SMS_LOGIN = LOGIN_PATH + "/sms";
}
/**
* OTP
*/
public static final String SMS_SEND_OTP = SMS_LOGIN + "/send";
public static final String PHONE_KEY = "phone";
public static final String CODE_KEY = "code";
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - Employee Identity and Access Management Program
* 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
@ -15,24 +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.portal.mfa;
package cn.topiam.employee.authentication.sms.exception;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.support.exception.TopIamException;
/**
* Sms
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/2 13:00
*/
public class SmsProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
return true;
public class CaptchaNotExistException extends TopIamException {
public CaptchaNotExistException() {
super("captcha_not_exist", "验证码不存在", DEFAULT_STATUS);
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-portal - Employee Identity and Access Management Program
* 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
@ -15,24 +15,19 @@
* 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.portal.mfa;
package cn.topiam.employee.authentication.sms.exception;
import cn.topiam.employee.core.security.mfa.MfaProviderValidator;
import cn.topiam.employee.support.exception.TopIamException;
/**
* Email
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/7/31 20:50
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/1/2 12:59
*/
public class EmailProviderValidator implements MfaProviderValidator {
/**
*
*
* @param code {@link String}
*/
@Override
public boolean validate(String code) {
return true;
public class PhoneNotExistException extends TopIamException {
public PhoneNotExistException() {
super("phone_not_exist", "手机号不存在", DEFAULT_STATUS);
}
}

View File

@ -51,8 +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.common.IdentityProviderType.WECHAT_QR;
import static cn.topiam.employee.authentication.common.constant.AuthenticationConstants.PROVIDER_CODE;
import static cn.topiam.employee.authentication.wechat.constant.WeChatAuthenticationConstants.*;
import static cn.topiam.employee.common.enums.IdentityProviderType.WECHAT_SCAN_CODE;
/**
*
@ -66,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());
/**
@ -107,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("未查询到身份提供商信息");
}
@ -124,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
@ -160,13 +156,13 @@ public class WeChatScanCodeAuthorizationRequestRedirectFilter extends OncePerReq
authorizationRequest.getAuthorizationRequestUri());
}
private final static String STYLE = ""
private static final String STYLE = ""
+ ".impowerBox .qrcode {width: 280px;border: none;margin-top:10px;}\n"
+ ".impowerBox .title {display: none;}\n"
+ ".impowerBox .info {display: none;}\n"
+ ".status_icon {display: none}\n"
+ ".impowerBox .status {text-align: center;} ";
private final static String STYLE_BASE64 = "data:text/css;base64," + Base64.getEncoder()
private static final String STYLE_BASE64 = "data:text/css;base64," + Base64.getEncoder()
.encodeToString(STYLE.getBytes(StandardCharsets.UTF_8));
public static RequestMatcher getRequestMatcher() {

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