diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java index 6d716e7a..46e1d915 100644 --- a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/AbstractCasApplicationService.java @@ -17,9 +17,14 @@ */ package cn.topiam.employee.application.cas; -import cn.topiam.employee.application.ApplicationService; -import cn.topiam.employee.common.repository.app.AppCertRepository; -import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.application.AbstractApplicationService; +import cn.topiam.employee.application.CasApplicationService; +import cn.topiam.employee.common.entity.app.po.AppCasConfigPO; +import cn.topiam.employee.common.repository.app.*; +import cn.topiam.employee.core.protocol.CasSsoModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; /** * CAS 应用配置 @@ -27,20 +32,52 @@ import cn.topiam.employee.common.repository.app.AppRepository; * @author TopIAM * Created by support@topiam.cn on 2022/8/23 20:58 */ -public abstract class AbstractCasApplicationService implements ApplicationService { +public abstract class AbstractCasApplicationService extends AbstractApplicationService + implements CasApplicationService { + + private static final Logger logger = LoggerFactory + .getLogger(AbstractCasApplicationService.class); - /** - * AppCertRepository - */ - protected final AppCertRepository appCertRepository; /** * ApplicationRepository */ - protected final AppRepository appRepository; + protected final AppRepository appRepository; + + protected final AppCasConfigRepository appCasConfigRepository; protected AbstractCasApplicationService(AppCertRepository appCertRepository, - AppRepository appRepository) { - this.appCertRepository = appCertRepository; + AppAccountRepository appAccountRepository, + AppAccessPolicyRepository appAccessPolicyRepository, + AppRepository appRepository, + AppCasConfigRepository appCasConfigRepository) { + super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository); this.appRepository = appRepository; + this.appCasConfigRepository = appCasConfigRepository; + } + + @Override + public CasSsoModel getSsoModel(Long appId) { + AppCasConfigPO appCasConfigPO = appCasConfigRepository.getByAppId(appId); + return CasSsoModel.builder().ssoCallbackUrl(appCasConfigPO.getSpCallbackUrl()).build(); + } + + /** + * 删除应用 + * + * @param appId {@link String} 应用ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(String appId) { + //删除应用 + appRepository.deleteById(Long.valueOf(appId)); + //删除证书 + appCertRepository.deleteByAppId(Long.valueOf(appId)); + //删除应用账户 + appAccountRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除应用权限策略 + appAccessPolicyRepository.deleteAllByAppId(Long.valueOf(appId)); + //删除配置 + appCasConfigRepository.deleteByAppId(Long.valueOf(appId)); } } diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java index de154eb9..883296e9 100644 --- a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/CasStandardApplicationServiceImpl.java @@ -17,15 +17,34 @@ */ package cn.topiam.employee.application.cas; -import java.util.List; -import java.util.Map; - -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.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.repository.app.AppCertRepository; -import cn.topiam.employee.common.repository.app.AppRepository; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintViolationException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE_VARIABLE; +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; /** * Cas 用户应用 @@ -35,15 +54,72 @@ import cn.topiam.employee.common.repository.app.AppRepository; */ @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; + } /** * 更新应用配置 * - * @param appId {@link String} + * @param appId {@link String} * @param config {@link Map} */ @Override public void saveConfig(String appId, Map config) { + AppCasStandardSaveConfigParam model; + try { + String value = mapper.writeValueAsString(config); + // 指定序列化输入的类型 + mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + model = mapper.readValue(value, AppCasStandardSaveConfigParam.class); + } catch (Exception e) { + throw new TopIamException(e.getMessage()); + } + + ValidationHelp.ValidationResult validationResult = ValidationHelp + .validateEntity(model); + if (validationResult.isHasErrors()) { + throw new ConstraintViolationException(validationResult.getConstraintViolations()); + } + + //1、修改基本信息 + Optional optional = appRepository.findById(Long.valueOf(appId)); + if (optional.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppEntity appEntity = optional.get(); + appEntity.setAuthorizationType(model.getAuthorizationType()); + appEntity.setInitLoginUrl(model.getInitLoginUrl()); + appEntity.setInitLoginType(model.getInitLoginType()); + appRepository.save(appEntity); + + //2、修改cas配置 + Optional cas = appCasConfigRepository.findByAppId(Long.valueOf(appId)); + if (cas.isEmpty()) { + AuditContext.setContent("保存配置失败,应用 [" + appId + "] 不存在!"); + logger.error(AuditContext.getContent()); + throw new AppNotExistException(); + } + AppCasConfigEntity entity = cas.get(); + entity.setSpCallbackUrl(model.getSpCallbackUrl()); + appCasConfigRepository.save(entity); + } /** @@ -54,7 +130,19 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer */ @Override public Object getConfig(String appId) { - return null; + 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; } /** @@ -114,7 +202,7 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer */ @Override public List getFormSchema() { - return null; + return new ArrayList<>(); } /** @@ -135,21 +223,27 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer */ @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(AppType.STANDARD); + 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); + + AppCasConfigEntity casEntity = new AppCasConfigEntity(); + casEntity.setAppId(appEntity.getId()); + casEntity.setSpCallbackUrl(""); + appCasConfigRepository.save(casEntity); + return appEntity.getId().toString(); } - /** - * 删除应用 - * - * @param appId {@link String} 应用ID - */ - @Override - public void delete(String appId) { - - } - - protected CasStandardApplicationServiceImpl(AppCertRepository appCertRepository, - AppRepository appRepository) { - super(appCertRepository, appRepository); - } } diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/converter/AppCasStandardConfigConverter.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/converter/AppCasStandardConfigConverter.java new file mode 100644 index 00000000..9fec2453 --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/converter/AppCasStandardConfigConverter.java @@ -0,0 +1,13 @@ +package cn.topiam.employee.application.cas.converter; + +import org.mapstruct.Mapper; + +/** + * 配置转换 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 17:31 + */ +@Mapper(componentModel = "spring") +public interface AppCasStandardConfigConverter { +} diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardConfigGetResult.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardConfigGetResult.java new file mode 100644 index 00000000..b0fc3dcf --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardConfigGetResult.java @@ -0,0 +1,45 @@ +package cn.topiam.employee.application.cas.model; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +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 = "授权类型") + private AuthorizationType authorizationType; + + /** + * SSO 发起登录类型 + */ + @Schema(description = "SSO 发起登录类型") + private InitLoginType initLoginType; + + /** + * SSO 发起登录URL + */ + @Schema(description = "SSO 发起登录URL") + private String initLoginUrl; + + /** + * 单点登录 SP 回调地址 + */ + @Parameter(name = "单点登录 sp Callback Url") + private String spCallbackUrl; + + /** + * Server端配置前缀 + */ + private String serverUrlPrefix; +} diff --git a/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardSaveConfigParam.java b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardSaveConfigParam.java new file mode 100644 index 00000000..5550254e --- /dev/null +++ b/eiam-application/eiam-application-cas/src/main/java/cn/topiam/employee/application/cas/model/AppCasStandardSaveConfigParam.java @@ -0,0 +1,44 @@ +package cn.topiam.employee.application.cas.model; + +import cn.topiam.employee.common.enums.app.AuthorizationType; +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; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 22:27 + */ +@Data +public class AppCasStandardSaveConfigParam implements Serializable { + @Serial + private static final long serialVersionUID = 1881187724713984421L; + + /** + * 应用ID + */ + @Schema(description = "授权类型") + private AuthorizationType authorizationType; + + /** + * SSO 发起登录类型 + */ + @Schema(description = "SSO 发起登录类型") + private InitLoginType initLoginType; + + /** + * SSO 发起登录URL + */ + @Schema(description = "SSO 发起登录URL") + private String initLoginUrl; + + /** + * 单点登录 SP 回调地址 + */ + @Parameter(name = "单点登录 sp Callback Url") + private String spCallbackUrl; +} diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java index f95d28ee..1115fd62 100644 --- a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/AbstractApplicationService.java @@ -17,15 +17,6 @@ */ package cn.topiam.employee.application; -import java.math.BigInteger; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Date; - -import org.bouncycastle.asn1.x500.X500Name; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import cn.topiam.employee.common.entity.app.AppCertEntity; import cn.topiam.employee.common.enums.app.AppCertUsingType; import cn.topiam.employee.common.repository.app.AppAccessPolicyRepository; @@ -35,6 +26,18 @@ import cn.topiam.employee.common.repository.app.AppRepository; import cn.topiam.employee.support.exception.TopIamException; import cn.topiam.employee.support.util.CertUtils; import cn.topiam.employee.support.util.RsaUtils; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.bouncycastle.asn1.x500.X500Name; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + import static cn.topiam.employee.support.util.CertUtils.encodePem; import static cn.topiam.employee.support.util.CertUtils.getX500Name; import static cn.topiam.employee.support.util.RsaUtils.getKeys; @@ -46,13 +49,14 @@ import static cn.topiam.employee.support.util.RsaUtils.getKeys; * Created by support@topiam.cn on 2022/8/31 22:34 */ public abstract class AbstractApplicationService implements ApplicationService { - private final Logger logger = LoggerFactory.getLogger(AbstractApplicationService.class); + private final Logger logger = LoggerFactory.getLogger(AbstractApplicationService.class); + protected final ObjectMapper mapper = new ObjectMapper(); /** * 创建证书 * * @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) { @@ -115,7 +119,7 @@ public abstract class AbstractApplicationService implements ApplicationService { protected final AppAccountRepository appAccountRepository; /** - *AppAccessPolicyRepository + * AppAccessPolicyRepository */ protected final AppAccessPolicyRepository appAccessPolicyRepository; @@ -124,6 +128,11 @@ public abstract class AbstractApplicationService implements ApplicationService { */ protected final AppRepository appRepository; + /** + * IdGenerator + */ + protected final IdGenerator idGenerator; + protected AbstractApplicationService(AppCertRepository appCertRepository, AppAccountRepository appAccountRepository, AppAccessPolicyRepository appAccessPolicyRepository, @@ -132,5 +141,6 @@ public abstract class AbstractApplicationService implements ApplicationService { this.appAccountRepository = appAccountRepository; this.appAccessPolicyRepository = appAccessPolicyRepository; this.appRepository = appRepository; + this.idGenerator = new AlternativeJdkIdGenerator(); } } diff --git a/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/CasApplicationService.java b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/CasApplicationService.java new file mode 100644 index 00000000..2e950e7d --- /dev/null +++ b/eiam-application/eiam-application-core/src/main/java/cn/topiam/employee/application/CasApplicationService.java @@ -0,0 +1,18 @@ +package cn.topiam.employee.application; + +import cn.topiam.employee.core.protocol.CasSsoModel; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 11:50 + */ +public interface CasApplicationService extends ApplicationService { + + /** + * 获取SSO Modal + * + * @param appId {@link String} + * @return {@link CasSsoModel} + */ + CasSsoModel getSsoModel(Long appId); +} diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java index c74c9101..7bb227b1 100644 --- a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/AbstractSamlAppService.java @@ -26,9 +26,7 @@ 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; @@ -136,11 +134,6 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService */ protected final AppSaml2ConfigRepository appSaml2ConfigRepository; - /** - * IdGenerator - */ - protected final IdGenerator idGenerator; - protected AbstractSamlAppService(AppCertRepository appCertRepository, AppAccountRepository appAccountRepository, AppAccessPolicyRepository appAccessPolicyRepository, @@ -148,7 +141,6 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService AppSaml2ConfigRepository appSaml2ConfigRepository) { super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository); this.appSaml2ConfigRepository = appSaml2ConfigRepository; - this.idGenerator = new AlternativeJdkIdGenerator(); } @Mapper(componentModel = "spring") diff --git a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java index 6d771996..9674c251 100644 --- a/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java +++ b/eiam-application/eiam-application-saml2/src/main/java/cn/topiam/employee/application/saml2/Saml2StandardApplicationServiceImpl.java @@ -71,7 +71,6 @@ public class Saml2StandardApplicationServiceImpl extends AbstractSamlAppService public void saveConfig(String appId, Map config) { AppSaml2StandardSaveConfigParam model; try { - ObjectMapper mapper = new ObjectMapper(); String value = mapper.writeValueAsString(config); // 指定序列化输入的类型 mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java index ca7c158a..80af3830 100644 --- a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ConfigBeanNameConstants.java @@ -35,6 +35,7 @@ public final class ConfigBeanNameConstants { public static final String SOCIAL_SECURITY_FILTER_CHAIN = "socialSecurityFilterChain"; public static final String SAML2_PROTOCOL_SECURITY_FILTER_CHAIN = "saml2ProtocolSecurityFilterChain"; public static final String OIDC_PROTOCOL_SECURITY_FILTER_CHAIN = "oidcProtocolSecurityFilterChain"; + public static final String CAS_PROTOCOL_SECURITY_FILTER_CHAIN = "casProtocolSecurityFilterChain"; /** * 默认密码策略管理器 diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java index fe6b5132..305e7e05 100644 --- a/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java +++ b/eiam-common/src/main/java/cn/topiam/employee/common/constants/ProtocolConstants.java @@ -18,10 +18,10 @@ package cn.topiam.employee.common.constants; import lombok.Data; -import static com.nimbusds.openid.connect.sdk.op.OIDCProviderConfigurationRequest.OPENID_PROVIDER_WELL_KNOWN_PATH; import static cn.topiam.employee.common.constants.AppConstants.APP_CACHE_NAME_PREFIX; import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZE_PATH; +import static com.nimbusds.openid.connect.sdk.op.OIDCProviderConfigurationRequest.OPENID_PROVIDER_WELL_KNOWN_PATH; /** * Saml 常量 @@ -49,6 +49,11 @@ public final class ProtocolConstants { */ public static final String SAML2_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "saml"; + /** + * CAS 配置缓存名称 + */ + public static final String CAS_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "cas"; + /** * OIDC 配置缓存名称 */ @@ -70,19 +75,19 @@ public final class ProtocolConstants { */ public final static String OIDC_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/" + APP_CODE_VARIABLE; - public final static String OIDC_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oidc"; + public final static String OIDC_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH + "/oidc"; - public final static String OAUTH2_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH +"/oauth2"; + public final static String OAUTH2_AUTHORIZE_PATH = OIDC_AUTHORIZE_BASE_PATH + "/oauth2"; /** * OpenID Provider metadata. */ - public static final String WELL_KNOWN_OPENID_CONFIGURATION = OIDC_AUTHORIZE_PATH +OPENID_PROVIDER_WELL_KNOWN_PATH; + public static final String WELL_KNOWN_OPENID_CONFIGURATION = OIDC_AUTHORIZE_PATH + OPENID_PROVIDER_WELL_KNOWN_PATH; /** * Jwk Set Endpoint */ - public static final String JWK_SET_ENDPOINT = OIDC_AUTHORIZE_PATH + "/jwks"; + public static final String JWK_SET_ENDPOINT = OIDC_AUTHORIZE_PATH + "/jwks"; /** * OIDC Client Registration Endpoint @@ -92,27 +97,27 @@ public final class ProtocolConstants { /** * Authorization Endpoint */ - public static final String AUTHORIZATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/auth"; + public static final String AUTHORIZATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/auth"; /** * Token Endpoint */ - public static final String TOKEN_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/token"; + public static final String TOKEN_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/token"; /** * Jwk Revocation Endpoint */ - public static final String TOKEN_REVOCATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/revoke"; + public static final String TOKEN_REVOCATION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/revoke"; /** * Token Introspection Endpoint */ - public static final String TOKEN_INTROSPECTION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/introspect"; + public static final String TOKEN_INTROSPECTION_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/introspect"; /** * OIDC User Info Endpoint */ - public static final String OIDC_USER_INFO_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/userinfo"; + public static final String OIDC_USER_INFO_ENDPOINT = OAUTH2_AUTHORIZE_PATH + "/userinfo"; //@formatter:on } @@ -144,4 +149,27 @@ public final class ProtocolConstants { public static final String SAML_SSO_PATH = SAML2_AUTHORIZE_BASE_PATH + "/sso"; } + @Data + public static class CasEndpointConstants { + /** + * cas 根路径 + */ + public final static String CAS_AUTHORIZE_BASE_PATH = AUTHORIZE_PATH + "/cas/" + + APP_CODE_VARIABLE; + /* + * cas 登陆地址 + */ + public final static String CAS_LOGIN_PATH = CAS_AUTHORIZE_BASE_PATH + "/login"; + /* + * cas ticket校验地址 + */ + public final static String CAS_VALIDATE_PATH = CAS_AUTHORIZE_BASE_PATH + "/validate"; + + public final static String CAS_VALIDATE_V2_PATH = CAS_AUTHORIZE_BASE_PATH + + "/serviceValidate"; + + public final static String CAS_VALIDATE_V3_PATH = CAS_AUTHORIZE_BASE_PATH + + "/p3/serviceValidate"; + } + } diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCasConfigEntity.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCasConfigEntity.java new file mode 100644 index 00000000..f198d572 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/AppCasConfigEntity.java @@ -0,0 +1,61 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.entity.app; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.annotations.TypeDef; + +import com.vladmihalcea.hibernate.type.json.JsonStringType; + +import cn.topiam.employee.support.repository.domain.BaseEntity; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +/** + * APP CAS 配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/30 22:31 + */ +@Getter +@Setter +@ToString +@Entity +@Accessors(chain = true) +@Table(name = "app_cas_config") +@TypeDef(name = "json", typeClass = JsonStringType.class) +public class AppCasConfigEntity extends BaseEntity { + /** + * APP ID + */ + @Column(name = "app_id") + private Long appId; + + /** + * SP 接受回调地址 + */ + @Column(name = "sp_callback_url") + private String spCallbackUrl; + +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppCasConfigPO.java b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppCasConfigPO.java new file mode 100644 index 00000000..c0eb20dc --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/entity/app/po/AppCasConfigPO.java @@ -0,0 +1,70 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.entity.app.po; + +import cn.topiam.employee.common.entity.app.AppCasConfigEntity; +import cn.topiam.employee.common.enums.app.AuthorizationType; +import cn.topiam.employee.common.enums.app.InitLoginType; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * CAS 配置 po + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 22:46 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppCasConfigPO extends AppCasConfigEntity { + /** + * APP CODE + */ + private String appCode; + + /** + * 模版 + */ + private String appTemplate; + + /** + * 客户端ID + */ + private String clientId; + + /** + * 客户端秘钥 + */ + private String clientSecret; + + /** + * SSO 发起方 + */ + private InitLoginType initLoginType; + + /** + * SSO 登录链接 + */ + private String initLoginUrl; + + /** + * 授权范围 + */ + private AuthorizationType authorizationType; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepository.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepository.java new file mode 100644 index 00000000..06551531 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepository.java @@ -0,0 +1,80 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.repository.app; + +import java.util.Optional; + +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.entity.app.AppCasConfigEntity; +import static cn.topiam.employee.common.constants.ProtocolConstants.CAS_CONFIG_CACHE_NAME; + +/** + * AppCasConfigRepository + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 15:26 + */ +@Repository +@CacheConfig(cacheNames = { CAS_CONFIG_CACHE_NAME }) +public interface AppCasConfigRepository extends JpaRepository, + QuerydslPredicateExecutor, + AppCasConfigRepositoryCustomized { + + /** + * 按应用 ID 删除 + * + * @param appId {@link Long} + */ + @CacheEvict(allEntries = true) + void deleteByAppId(Long appId); + + /** + * delete + * + * @param id must not be {@literal null}. + */ + @CacheEvict(allEntries = true) + @Override + void deleteById(@NotNull Long id); + + /** + * save + * + * @param entity must not be {@literal null}. + * @param {@link S} + * @return {@link AppCasConfigEntity} + */ + @NotNull + @Override + @CacheEvict(allEntries = true) + S save(@NotNull S entity); + + /** + * 根据应用ID获取配置 + * + * @param appId {@link Long} + * @return {@link AppCasConfigEntity} + */ + Optional findByAppId(Long appId); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepositoryCustomized.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepositoryCustomized.java new file mode 100644 index 00000000..2581b4af --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/AppCasConfigRepositoryCustomized.java @@ -0,0 +1,42 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.repository.app; + +import cn.topiam.employee.common.entity.app.po.AppCasConfigPO; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 15:28 + */ +public interface AppCasConfigRepositoryCustomized { + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppCasConfigPO} + */ + AppCasConfigPO getByAppId(Long appId); + + /** + * 根据应用code获取应用 + * + * @param appCode {@link String} + * @return {@link AppCasConfigPO} + */ + AppCasConfigPO findByAppCode(String appCode); +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppCasConfigRepositoryCustomizedImpl.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppCasConfigRepositoryCustomizedImpl.java new file mode 100644 index 00000000..e1625ec0 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/AppCasConfigRepositoryCustomizedImpl.java @@ -0,0 +1,70 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.repository.app.impl; + +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.common.entity.app.po.AppCasConfigPO; +import cn.topiam.employee.common.entity.app.po.AppSaml2ConfigPO; +import cn.topiam.employee.common.repository.app.AppCasConfigRepositoryCustomized; +import cn.topiam.employee.common.repository.app.impl.mapper.AppCasConfigPoMapper; + +import lombok.AllArgsConstructor; + +/** + * AppCasConfigRepositoryCustomizedImpl + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:37 + */ + +@Repository +@AllArgsConstructor +@CacheConfig(cacheNames = { ProtocolConstants.CAS_CONFIG_CACHE_NAME }) +public class AppCasConfigRepositoryCustomizedImpl implements AppCasConfigRepositoryCustomized { + /** + * 根据应用ID获取 + * + * @param appId {@link Long} + * @return {@link AppSaml2ConfigPO} + */ + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppCasConfigPO getByAppId(Long appId) { + String sql = "select acc.*,app.init_login_url,app.init_login_type,app.authorization_type,app.template_,app.code_,client_id,client_secret from app left join app_cas_config acc on app.id_ = acc.app_id where 1=1" + + " AND app_id = " + appId; + return jdbcTemplate.queryForObject(sql, new AppCasConfigPoMapper()); + } + + @Override + @Cacheable(key = "#p0", unless = "#result==null") + public AppCasConfigPO findByAppCode(String appCode) { + String sql = "select acc.*,app.init_login_url,app.init_login_type,app.authorization_type,app.template_,app.code_,client_id,client_secret from app left join app_cas_config acc on app.id_ = acc.app_id where 1=1" + + " AND code_ = " + "'" + appCode + "'"; + return jdbcTemplate.queryForObject(sql, new AppCasConfigPoMapper()); + } + + /** + * JdbcTemplate + */ + private final JdbcTemplate jdbcTemplate; +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppCasConfigPoMapper.java b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppCasConfigPoMapper.java new file mode 100644 index 00000000..6a69de95 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/repository/app/impl/mapper/AppCasConfigPoMapper.java @@ -0,0 +1,55 @@ +/* + * eiam-common - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.common.repository.app.impl.mapper; + +import cn.topiam.employee.common.entity.app.po.AppCasConfigPO; +import cn.topiam.employee.common.enums.app.InitLoginType; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; + +/** + * AppCasConfigPOPOMapper + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:31 + */ +public class AppCasConfigPoMapper implements RowMapper { + + @Override + public AppCasConfigPO mapRow(ResultSet rs, int rowNum) throws SQLException { + AppCasConfigPO configPO = new AppCasConfigPO(); + configPO.setAppId(rs.getLong("id_")); + configPO.setAppId(rs.getLong("app_id")); + configPO.setAppCode(rs.getString("code_")); + configPO.setClientId(rs.getString("client_id")); + configPO.setClientSecret(rs.getString("client_secret")); + configPO.setInitLoginType(InitLoginType.getType(rs.getString("init_login_type"))); + configPO.setInitLoginUrl(rs.getString("init_login_url")); + configPO.setAppTemplate(rs.getString("template_")); + configPO.setCreateBy(rs.getString("create_by")); + configPO.setCreateTime(rs.getObject("create_time", LocalDateTime.class)); + configPO.setUpdateBy(rs.getString("update_by")); + configPO.setCreateTime(rs.getObject("update_time", LocalDateTime.class)); + configPO.setRemark(rs.getString("remark_")); + configPO.setSpCallbackUrl(rs.getString("sp_callback_url")); + return configPO; + } +} diff --git a/eiam-common/src/main/java/cn/topiam/employee/common/util/CasUtils.java b/eiam-common/src/main/java/cn/topiam/employee/common/util/CasUtils.java new file mode 100644 index 00000000..320ce806 --- /dev/null +++ b/eiam-common/src/main/java/cn/topiam/employee/common/util/CasUtils.java @@ -0,0 +1,13 @@ +package cn.topiam.employee.common.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/30 01:06 + */ +public class CasUtils { + private final static Logger logger = LoggerFactory.getLogger(CasUtils.class); + +} diff --git a/eiam-common/src/main/resources/db/changelog/app_cas_config-changelog.xml b/eiam-common/src/main/resources/db/changelog/app_cas_config-changelog.xml new file mode 100644 index 00000000..972b35cf --- /dev/null +++ b/eiam-common/src/main/resources/db/changelog/app_cas_config-changelog.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eiam-core/src/main/java/cn/topiam/employee/core/protocol/CasSsoModel.java b/eiam-core/src/main/java/cn/topiam/employee/core/protocol/CasSsoModel.java new file mode 100644 index 00000000..86d41275 --- /dev/null +++ b/eiam-core/src/main/java/cn/topiam/employee/core/protocol/CasSsoModel.java @@ -0,0 +1,18 @@ +package cn.topiam.employee.core.protocol; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 11:50 + */ +@Data +@Builder +public class CasSsoModel implements Serializable { + + private String ssoCallbackUrl; + +} diff --git a/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalSecurityConfiguration.java b/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalSecurityConfiguration.java index f6fb8312..af03e63b 100644 --- a/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalSecurityConfiguration.java +++ b/eiam-portal/src/main/java/cn/topiam/employee/portal/configuration/PortalSecurityConfiguration.java @@ -105,6 +105,7 @@ import cn.topiam.employee.portal.listener.PortalAuthenticationSuccessEventListen import cn.topiam.employee.portal.listener.PortalLogoutSuccessEventListener; import cn.topiam.employee.portal.listener.PortalSessionInformationExpiredStrategy; import cn.topiam.employee.portal.mfa.MfaAuthenticationConfigurer; +import cn.topiam.employee.protocol.cas.idp.CasIdpConfigurer; import cn.topiam.employee.protocol.oidc.authentication.EiamOAuth2AuthorizationService; import cn.topiam.employee.protocol.oidc.repository.OidcConfigRegisteredClientRepository; import cn.topiam.employee.protocol.oidc.token.ApplicationOpaqueTokenIntrospector; @@ -230,6 +231,36 @@ public class PortalSecurityConfiguration { return http.build(); } + /** + * CAS 协议 + * + * @param http {@link HttpSecurity} + * @return {@link SecurityFilterChain} + * @throws Exception Exception + */ + @Order(3) + @Bean(value = CAS_PROTOCOL_SECURITY_FILTER_CHAIN) + @RefreshScope + public SecurityFilterChain casProtocolSecurityFilterChain(HttpSecurity http) throws Exception { + CasIdpConfigurer configurer = new CasIdpConfigurer<>(); + RequestMatcher endpointsMatcher = configurer.getEndpointsMatcher(); + http.requestMatcher(endpointsMatcher) + .authorizeHttpRequests( + authorizeRequests -> authorizeRequests.anyRequest().authenticated()) + //异常处理 + .exceptionHandling(withExceptionConfigurerDefaults()) + //CSRF + .csrf(withCsrfConfigurerDefaults(endpointsMatcher)) + //headers + .headers(withHeadersConfigurerDefaults()) + //cors + .cors(withCorsConfigurerDefaults()) + //会话管理器 + .sessionManagement(withSessionManagementConfigurerDefaults(settingRepository)) + .apply(configurer); + return http.build(); + } + /** * SAML 协议 * diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/CasIdpConfigurer.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/CasIdpConfigurer.java new file mode 100644 index 00000000..3d4d8c94 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/CasIdpConfigurer.java @@ -0,0 +1,80 @@ +/* + * eiam-protocol-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.protocol.cas.idp; + +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.common.repository.app.AppCasConfigRepository; +import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; +import cn.topiam.employee.protocol.cas.idp.endpoint.CasIdpSingleSignOnEndpointFilter; +import cn.topiam.employee.protocol.cas.idp.endpoint.CasIdpValidateEndpointFilter; +import cn.topiam.employee.protocol.cas.idp.filter.CasAuthorizationServerContextFilter; +import cn.topiam.employee.protocol.cas.idp.util.CasUtils; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import javax.xml.parsers.DocumentBuilder; +import java.util.ArrayList; +import java.util.List; + +import static cn.topiam.employee.protocol.cas.idp.util.CasUtils.*; + +/** + * 认证配置 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:23 + */ +public class CasIdpConfigurer> + extends AbstractHttpConfigurer, B> { + + @Override + public void configure(B http) { + AppCasConfigRepository appCasConfigRepository = CasUtils.getAppCasConfigRepository(http); + ApplicationServiceLoader applicationServiceLoader = getApplicationServiceLoader(http); + SessionRegistry sessionRegistry = getSessionRegistry(http); + CentralAuthenticationService centralAuthenticationService = getCentralAuthenticationService( + http); + DocumentBuilder documentBuilder = getDocumentBuilder(http); + //CAS 登陆过滤器 + http.addFilterAfter(new CasIdpSingleSignOnEndpointFilter(applicationServiceLoader, + sessionRegistry, centralAuthenticationService), + UsernamePasswordAuthenticationFilter.class); + + //cas 验证过滤器 + http.addFilterBefore( + new CasIdpValidateEndpointFilter(applicationServiceLoader, sessionRegistry, + centralAuthenticationService, documentBuilder), + CasIdpSingleSignOnEndpointFilter.class); + + //CAS 授权服务器应用上下文过滤器 + http.addFilterBefore( + new CasAuthorizationServerContextFilter(getEndpointsMatcher(), appCasConfigRepository), + CasIdpValidateEndpointFilter.class); + } + + public RequestMatcher getEndpointsMatcher() { + List requestMatchers = new ArrayList<>(); + requestMatchers.add(CasIdpSingleSignOnEndpointFilter.getRequestMatcher()); + requestMatchers.add(CasIdpValidateEndpointFilter.getRequestMatcher()); + return new OrRequestMatcher(requestMatchers); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/AuthenticationContext.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/AuthenticationContext.java new file mode 100644 index 00000000..edf0fce6 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/AuthenticationContext.java @@ -0,0 +1,8 @@ +package cn.topiam.employee.protocol.cas.idp.auth; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 23:47 + */ +public class AuthenticationContext { +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationService.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationService.java new file mode 100644 index 00000000..72cf0d37 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationService.java @@ -0,0 +1,22 @@ +package cn.topiam.employee.protocol.cas.idp.auth; + +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket; +import cn.topiam.employee.protocol.cas.idp.tickets.Ticket; +import cn.topiam.employee.protocol.cas.idp.tickets.TicketGrantingTicket; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 23:43 + */ +public interface CentralAuthenticationService { + TicketGrantingTicket createTicketGrantingTicket(UserDetails userDetails, String sessionId); + + ServiceTicket grantServiceTicket(String tgtId, String service); + + T getTicket(String id, Class clazz); + + ServiceTicket validateServiceTicket(String id, String service); + + void destroyTicketGrantingTicket(String var1); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationServiceImp.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationServiceImp.java new file mode 100644 index 00000000..d70d0f71 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralAuthenticationServiceImp.java @@ -0,0 +1,72 @@ +package cn.topiam.employee.protocol.cas.idp.auth; + +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.protocol.cas.idp.tickets.*; +import com.google.common.base.Preconditions; +import org.springframework.stereotype.Service; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/1 16:25 + */ +@Service(value = "cas-authentication-service") +public class CentralAuthenticationServiceImp implements CentralAuthenticationService { + final TicketRegistry ticketRegistry; + final TicketFactory ticketFactory; + + public CentralAuthenticationServiceImp(TicketRegistry ticketRegistry, + TicketFactory ticketFactory) { + this.ticketRegistry = ticketRegistry; + this.ticketFactory = ticketFactory; + } + + @Override + public TicketGrantingTicket createTicketGrantingTicket(UserDetails userDetails, + String sessionId) { + TicketGrantingTicketFactory ticketGrantingTicketFactory = ticketFactory + .get(TicketGrantingTicket.class); + TicketGrantingTicket ticketGrantingTicket = ticketGrantingTicketFactory.create(userDetails, + sessionId); + ticketRegistry.addTicket(ticketGrantingTicket); + return ticketGrantingTicket; + } + + @Override + public ServiceTicket grantServiceTicket(final String tgtId, final String service) { + TicketGrantingTicket ticketGrantingTicket = this.getTicket(tgtId, + TicketGrantingTicket.class); + ServiceTicketFactory serviceTicketFactory = ticketFactory.get(ServiceTicket.class); + ServiceTicket serviceTicket = serviceTicketFactory.create(ticketGrantingTicket, service); + ticketRegistry.addTicket(serviceTicket); + return serviceTicket; + } + + @Override + public T getTicket(String id, Class clazz) { + return ticketRegistry.getTicket(id, clazz); + } + + @Override + public ServiceTicket validateServiceTicket(String serviceTicketId, String service) { + try { + ServiceTicket serviceTicket = ticketRegistry + .getTicket(Preconditions.checkNotNull(serviceTicketId), ServiceTicket.class); + TicketGrantingTicket ticketGrantingTicket = Preconditions.checkNotNull(serviceTicket) + .getTicketGrantingTicket(); + Preconditions.checkNotNull(ticketGrantingTicket); + return serviceTicket; + } catch (NullPointerException e) { + return null; + } finally { + this.ticketRegistry.deleteTicket(serviceTicketId); + } + } + + @Override + public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) { + TicketGrantingTicket ticketGrantingTicket = this.getTicket(ticketGrantingTicketId, + TicketGrantingTicket.class); + // TODO: 通知客户端销毁ticket + ticketRegistry.deleteTicket(ticketGrantingTicketId); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralCacheService.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralCacheService.java new file mode 100644 index 00000000..6a6bf7e3 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/auth/CentralCacheService.java @@ -0,0 +1,46 @@ +package cn.topiam.employee.protocol.cas.idp.auth; + +import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket; +import cn.topiam.employee.protocol.cas.idp.tickets.Ticket; +import cn.topiam.employee.protocol.cas.idp.tickets.TicketGrantingTicket; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 21:42 + */ +@SuppressWarnings("ALL") +@Service +public class CentralCacheService { + private static final int DEFAULT_ST_EXPIRED_TIME = 10; + //将Service Ticket存放到redis,一次性使用,10秒内过期 + private static final int DEFAULT_TGT_EXPIRED_TIME = 7200; + //TicketGrantingTicket 默认两个小时过期 + private final RedisTemplate redisTemplate; + + public CentralCacheService(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public void save(Ticket ticket) { + long timeout = 0; + if (ticket instanceof ServiceTicket) { + timeout = DEFAULT_ST_EXPIRED_TIME; + } + if (ticket instanceof TicketGrantingTicket) { + timeout = DEFAULT_TGT_EXPIRED_TIME; + } + redisTemplate.opsForValue().set(ticket.getId(), ticket, timeout, TimeUnit.SECONDS); + } + + public Ticket get(String id) { + return (Ticket) redisTemplate.opsForValue().get(id); + } + + public void remove(String id) { + redisTemplate.delete(id); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/configuration/CasConfiguration.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/configuration/CasConfiguration.java new file mode 100644 index 00000000..2ba45200 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/configuration/CasConfiguration.java @@ -0,0 +1,30 @@ +package cn.topiam.employee.protocol.cas.idp.configuration; + +import cn.topiam.employee.protocol.cas.idp.tickets.DefaultTicketFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/30 01:03 + */ +@Configuration +public class CasConfiguration { + + @Bean + public DefaultTicketFactory factory() { + final DefaultTicketFactory factory = new DefaultTicketFactory(); + factory.initialize(); + return factory; + } + + @Bean + public DocumentBuilder documentBuilder() throws ParserConfigurationException { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/constant/ProtocolConstants.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/constant/ProtocolConstants.java new file mode 100644 index 00000000..9d90039a --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/constant/ProtocolConstants.java @@ -0,0 +1,28 @@ +package cn.topiam.employee.protocol.cas.idp.constant; + +/** + * 变量 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 22:07 + */ +public class ProtocolConstants { + + public static final String PREFIX_ST = "ST"; + public static final String PREFIX_TGT = "TGT"; + + public static final String TICKET = "ticket"; + public static final String SERVICE = "service"; + + public static final String SERVICE_RESPONSE = "cas:serviceResponse"; + public static final String SERVICE_ATTRIBUTES = "http://www.yale.edu/tp/cas"; + + public static final String AUTHENTICATION_FAILED = "cas:authenticationFailure"; + public static final String AUTHENTICATION_SUCCESS = "cas:authenticationSuccess"; + + public static final String CAS_ATTRIBUTES = "cas:attributes"; + public static final String CAS_USER = "cas:user"; + + public static final String INVALID_TICKET = "INVALID_TICKET"; + +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpSingleSignOnEndpointFilter.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpSingleSignOnEndpointFilter.java new file mode 100644 index 00000000..34480424 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpSingleSignOnEndpointFilter.java @@ -0,0 +1,131 @@ +package cn.topiam.employee.protocol.cas.idp.endpoint; + +import cn.topiam.employee.application.ApplicationService; +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.application.CasApplicationService; +import cn.topiam.employee.application.context.ApplicationContext; +import cn.topiam.employee.application.context.ApplicationContextHolder; +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.core.context.ServerContextHelp; +import cn.topiam.employee.core.protocol.CasSsoModel; +import cn.topiam.employee.core.security.savedredirect.HttpSessionRedirectCache; +import cn.topiam.employee.core.security.savedredirect.RedirectCache; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.core.security.util.SecurityUtils; +import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; +import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket; +import cn.topiam.employee.protocol.cas.idp.tickets.TicketGrantingTicket; +import cn.topiam.employee.support.exception.TopIamException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.servlet.filter.OrderedFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.session.SessionInformation; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.CollectionUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +import static cn.topiam.employee.common.constants.AuthorizeConstants.FE_LOGIN; +import static cn.topiam.employee.core.security.util.SecurityUtils.isAuthenticated; +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.SERVICE; +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TICKET; + +/** + * CAS 单点登录 + * + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 17:38 + */ +@SuppressWarnings("DuplicatedCode") +public class CasIdpSingleSignOnEndpointFilter extends OncePerRequestFilter + implements OrderedFilter { + private static final Logger logger = LoggerFactory + .getLogger(CasIdpSingleSignOnEndpointFilter.class); + private static final RequestMatcher CAS_SSO_REQUEST_MATCHER = new AntPathRequestMatcher( + ProtocolConstants.CasEndpointConstants.CAS_LOGIN_PATH, HttpMethod.GET.name()); + private final RedirectCache redirectCache = new HttpSessionRedirectCache(); + + /** + * 应用配置 + */ + private final ApplicationServiceLoader applicationServiceLoader; + + private final SessionRegistry sessionRegistry; + + private final CentralAuthenticationService centralAuthenticationService; + + public CasIdpSingleSignOnEndpointFilter(ApplicationServiceLoader applicationServiceLoader, + SessionRegistry sessionRegistry, + CentralAuthenticationService centralAuthenticationService) { + this.applicationServiceLoader = applicationServiceLoader; + this.sessionRegistry = sessionRegistry; + this.centralAuthenticationService = centralAuthenticationService; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (CAS_SSO_REQUEST_MATCHER.matches(request)) { + if (!isAuthenticated()) { + //Saved Redirect + if (!CollectionUtils.isEmpty(request.getParameterMap())) { + redirectCache.saveRedirect(request, response, + RedirectCache.RedirectType.REQUEST); + } + //跳转登录 + response.sendRedirect(ServerContextHelp.getPortalPublicBaseUrl() + FE_LOGIN); + return; + } + UserDetails userDetails = SecurityUtils.getCurrentUser(); + List sessionInformations = sessionRegistry + .getAllSessions(userDetails.getUsername(), false); + if (sessionInformations.size() != 1) { + throw new TopIamException("用户身份出现异常"); + } + String sessionId = sessionInformations.get(0).getSessionId(); + //获取应用配置 + ApplicationContext applicationContext = ApplicationContextHolder + .getApplicationContext(); + ApplicationService applicationService = applicationServiceLoader + .getApplicationService(applicationContext.getAppTemplate()); + CasSsoModel ssoModel = ((CasApplicationService) applicationService) + .getSsoModel(applicationContext.getAppId()); + + String service = request.getParameter(SERVICE); + + TicketGrantingTicket ticketGrantingTicket = centralAuthenticationService + .getTicket(sessionId, TicketGrantingTicket.class); + + if (ticketGrantingTicket == null) { + ticketGrantingTicket = centralAuthenticationService + .createTicketGrantingTicket(userDetails, sessionId); + } + ServiceTicket serviceTicket = centralAuthenticationService + .grantServiceTicket(ticketGrantingTicket.getId(), service); + + response.sendRedirect(UriComponentsBuilder.fromHttpUrl(ssoModel.getSsoCallbackUrl()) + .queryParam(TICKET, serviceTicket.getId()).build().toString()); + } + filterChain.doFilter(request, response); + } + + public static RequestMatcher getRequestMatcher() { + return CAS_SSO_REQUEST_MATCHER; + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpValidateEndpointFilter.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpValidateEndpointFilter.java new file mode 100644 index 00000000..7e9dbc05 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/endpoint/CasIdpValidateEndpointFilter.java @@ -0,0 +1,101 @@ +package cn.topiam.employee.protocol.cas.idp.endpoint; + +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; +import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket; +import cn.topiam.employee.protocol.cas.idp.xml.ResponseGenerator; +import cn.topiam.employee.protocol.cas.idp.xml.ResponseGeneratorImpl; +import org.springframework.boot.web.servlet.filter.OrderedFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpMethod; +import org.springframework.security.core.session.SessionRegistry; +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.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.SERVICE; +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TICKET; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 13:38 + */ +public class CasIdpValidateEndpointFilter extends OncePerRequestFilter implements OrderedFilter { + + private final ApplicationServiceLoader applicationServiceLoader; + + private final SessionRegistry sessionRegistry; + + private final CentralAuthenticationService centralAuthenticationService; + + private static final OrRequestMatcher orRequestMatcher; + + static { + List requestMatchers = new ArrayList<>(); + requestMatchers.add(new AntPathRequestMatcher( + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_PATH, HttpMethod.GET.name())); + requestMatchers.add(new AntPathRequestMatcher( + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V2_PATH, HttpMethod.GET.name())); + requestMatchers.add(new AntPathRequestMatcher( + ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V3_PATH, HttpMethod.GET.name())); + orRequestMatcher = new OrRequestMatcher(requestMatchers); + } + + private final DocumentBuilder documentBuilder; + + public CasIdpValidateEndpointFilter(ApplicationServiceLoader applicationServiceLoader, + SessionRegistry sessionRegistry, + CentralAuthenticationService centralAuthenticationService, + DocumentBuilder documentBuilder) { + this.applicationServiceLoader = applicationServiceLoader; + this.sessionRegistry = sessionRegistry; + this.centralAuthenticationService = centralAuthenticationService; + this.documentBuilder = documentBuilder; + + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if (orRequestMatcher.matches(request)) { + ResponseGenerator generator = new ResponseGeneratorImpl(documentBuilder, response); + String ticketId = request.getParameter(TICKET); + String service = request.getParameter(SERVICE); + ServiceTicket serviceTicket = centralAuthenticationService + .validateServiceTicket(ticketId, service); + if (serviceTicket == null) { + generator.genFailedMessage(ticketId); + } else { + UserDetails userDetails = serviceTicket.getTicketGrantingTicket().getUserDetails(); + // TODO: 2023/1/2 根据配置返回额外的属性配置 + generator.genSucceedMessage(userDetails.getUsername(), new HashMap<>()); + } + generator.sendMessage(); + } + filterChain.doFilter(request, response); + + } + + public static RequestMatcher getRequestMatcher() { + return orRequestMatcher; + } + +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/filter/CasAuthorizationServerContextFilter.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/filter/CasAuthorizationServerContextFilter.java new file mode 100644 index 00000000..3050b1cd --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/filter/CasAuthorizationServerContextFilter.java @@ -0,0 +1,72 @@ +package cn.topiam.employee.protocol.cas.idp.filter; + +import cn.topiam.employee.application.context.ApplicationContext; +import cn.topiam.employee.application.context.ApplicationContextHolder; +import cn.topiam.employee.application.exception.AppNotExistException; +import cn.topiam.employee.common.constants.ProtocolConstants; +import cn.topiam.employee.common.entity.app.po.AppCasConfigPO; +import cn.topiam.employee.common.repository.app.AppCasConfigRepository; +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 javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static cn.topiam.employee.common.constants.ProtocolConstants.APP_CODE; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 22:37 + */ +public class CasAuthorizationServerContextFilter extends OncePerRequestFilter { + + private final RequestMatcher endpointsMatcher; + private final AppCasConfigRepository appCasConfigRepository; + + private final RequestMatcher appAuthorizePathRequestMatcher = new AntPathRequestMatcher( + ProtocolConstants.CasEndpointConstants.CAS_AUTHORIZE_BASE_PATH + "/**"); + + public CasAuthorizationServerContextFilter(RequestMatcher endpointsMatcher, + AppCasConfigRepository appCasConfigRepository) { + Assert.notNull(endpointsMatcher, "endpointsMatcher cannot be null"); + Assert.notNull(appCasConfigRepository, "appCasConfigRepository cannot be null"); + this.endpointsMatcher = endpointsMatcher; + this.appCasConfigRepository = appCasConfigRepository; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + try { + //匹配 + if (appAuthorizePathRequestMatcher.matches(request) + && endpointsMatcher.matches(request)) { + //获取应用编码 + Map variables = appAuthorizePathRequestMatcher.matcher(request) + .getVariables(); + String appCode = variables.get(APP_CODE); + AppCasConfigPO configPo = appCasConfigRepository.findByAppCode(appCode); + if (Objects.isNull(configPo)) { + throw new AppNotExistException(); + } + + //封装上下文内容 + Map config = new HashMap<>(16); + ApplicationContextHolder.setProviderContext(new ApplicationContext( + configPo.getAppId(), configPo.getAppCode(), configPo.getAppTemplate(), + configPo.getClientId(), configPo.getClientSecret(), config)); + } + filterChain.doFilter(request, response); + } finally { + ApplicationContextHolder.resetProviderContext(); + } + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultServiceTicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultServiceTicketFactory.java new file mode 100644 index 00000000..bee254b9 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultServiceTicketFactory.java @@ -0,0 +1,21 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class DefaultServiceTicketFactory implements ServiceTicketFactory { + @Override + public ServiceTicket create(final TicketGrantingTicket ticketGrantingTicket, + final String service) { + if (ticketGrantingTicket == null) { + return null; + } + return ticketGrantingTicket.grantServiceTicket(service); + } + + @Override + public T get(final Class clazz) { + return (T) this; + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketFactory.java new file mode 100644 index 00000000..9bb45613 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketFactory.java @@ -0,0 +1,28 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class DefaultTicketFactory implements TicketFactory { + private Map factoryMap; + private ServiceTicketFactory serviceTicketFactory = new DefaultServiceTicketFactory(); + private TicketGrantingTicketFactory ticketGrantingTicketFactory = new DefaultTicketGrantingTicketFactory(); + + public void initialize() { + serviceTicketFactory = new DefaultServiceTicketFactory(); + ticketGrantingTicketFactory = new DefaultTicketGrantingTicketFactory(); + factoryMap = new HashMap<>(); + factoryMap.put(TicketGrantingTicket.class.getCanonicalName(), + this.ticketGrantingTicketFactory); + factoryMap.put(ServiceTicket.class.getCanonicalName(), this.serviceTicketFactory); + } + + @Override + public T get(final Class clazz) { + return (T) this.factoryMap.get(clazz.getCanonicalName()); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketGrantingTicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketGrantingTicketFactory.java new file mode 100644 index 00000000..371d0135 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketGrantingTicketFactory.java @@ -0,0 +1,21 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import cn.topiam.employee.core.security.userdetails.UserDetails; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class DefaultTicketGrantingTicketFactory implements TicketGrantingTicketFactory { + + @Override + public T get(final Class clazz) { + return (T) this; + } + + @Override + // TODO: 2023/1/2 TGT本来应该以TGT-xxx命名,此处为了兼容系统session,直接使用sessionId作为TGT的id + public TicketGrantingTicket create(UserDetails userDetails, String sessionId) { + return new TicketGrantingTicketImpl(sessionId, userDetails); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketRegistry.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketRegistry.java new file mode 100644 index 00000000..ffdaa29f --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/DefaultTicketRegistry.java @@ -0,0 +1,57 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import cn.topiam.employee.protocol.cas.idp.auth.CentralCacheService; +import com.google.common.base.Preconditions; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +@Slf4j +@Service +public class DefaultTicketRegistry implements TicketRegistry { + + CentralCacheService cacheService; + + public DefaultTicketRegistry(CentralCacheService cacheService) { + this.cacheService = cacheService; + } + + @Override + public void addTicket(final Ticket ticket) { + cacheService.save(ticket); + } + + @Override + public Ticket getTicket(final String ticketId) { + return cacheService.get(ticketId); + } + + @Override + public T getTicket(final String id, final Class clazz) { + Preconditions.checkNotNull(clazz, "clazz cannot be null"); + Ticket ticket = this.getTicket(id); + if (ticket == null) { + return null; + } + + if (!clazz.isAssignableFrom(ticket.getClass())) { + throw new ClassCastException("Ticket [" + ticket.getId() + " is of type " + + ticket.getClass() + " when we were expecting " + clazz); + } + return (T) ticket; + } + + @Override + public void updateTicket(final Ticket ticket) { + addTicket(ticket); + } + + @Override + public void deleteTicket(final String id) { + cacheService.remove(id); + } + +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicket.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicket.java new file mode 100644 index 00000000..50f4cf79 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicket.java @@ -0,0 +1,11 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface ServiceTicket extends Ticket { + TicketGrantingTicket getTicketGrantingTicket(); + + String getService(); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketFactory.java new file mode 100644 index 00000000..be605183 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketFactory.java @@ -0,0 +1,9 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface ServiceTicketFactory extends TicketFactory { + ServiceTicket create(TicketGrantingTicket ticketGrantingTicket, String service); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketImpl.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketImpl.java new file mode 100644 index 00000000..404ccb93 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/ServiceTicketImpl.java @@ -0,0 +1,30 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class ServiceTicketImpl extends TicketGrantingTicketImpl implements ServiceTicket { + private TicketGrantingTicket ticketGrantingTicket; + private String service; + + ServiceTicketImpl(final String id, final TicketGrantingTicket ticket, final String service) { + super(id, ticket.getUserDetails()); + this.ticketGrantingTicket = ticket; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public String getService() { + return this.service; + } + + @Override + public TicketGrantingTicket getTicketGrantingTicket() { + return ticketGrantingTicket; + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/Ticket.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/Ticket.java new file mode 100644 index 00000000..37e2c097 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/Ticket.java @@ -0,0 +1,15 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import java.io.Serializable; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface Ticket extends Serializable { + String getId(); + + boolean isExpired(); + + long getCreateTime(); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketFactory.java new file mode 100644 index 00000000..39acc9da --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketFactory.java @@ -0,0 +1,9 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface TicketFactory { + T get(Class clazz); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicket.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicket.java new file mode 100644 index 00000000..87bc6755 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicket.java @@ -0,0 +1,15 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import cn.topiam.employee.core.security.userdetails.UserDetails; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + *

+ * 其实TGT可以理解为用户登陆的session,包含了用户的信息 + */ +public interface TicketGrantingTicket extends Ticket { + ServiceTicket grantServiceTicket(String service); + + UserDetails getUserDetails(); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketFactory.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketFactory.java new file mode 100644 index 00000000..48efd46c --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketFactory.java @@ -0,0 +1,12 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import cn.topiam.employee.core.security.userdetails.UserDetails; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface TicketGrantingTicketFactory extends TicketFactory { + + TicketGrantingTicket create(UserDetails userDetails, String sessionId); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketImpl.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketImpl.java new file mode 100644 index 00000000..7028624b --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketGrantingTicketImpl.java @@ -0,0 +1,64 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +import cn.topiam.employee.core.security.userdetails.UserDetails; +import cn.topiam.employee.protocol.cas.idp.util.TicketUtils; + +import java.util.Objects; + +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.PREFIX_ST; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class TicketGrantingTicketImpl implements TicketGrantingTicket { + private final String id; + private final long createTime; + + private final UserDetails userDetails; + + TicketGrantingTicketImpl(final String id, final UserDetails userDetails) { + this.id = id; + this.userDetails = userDetails; + this.createTime = System.currentTimeMillis(); + } + + @Override + public String getId() { + return id; + } + + @Override + public boolean isExpired() { + return false; + } + + public UserDetails getUserDetails() { + return this.userDetails; + } + + @Override + public long getCreateTime() { + return createTime; + } + + @Override + public synchronized ServiceTicket grantServiceTicket(final String service) { + return new ServiceTicketImpl(TicketUtils.generateTicket(PREFIX_ST), this, service); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + Ticket ticket = (Ticket) obj; + return Objects.equals(this.id, ticket.getId()) + && Objects.equals(this.createTime, ticket.getCreateTime()); + } + + @Override + public int hashCode() { + return id.hashCode() + Long.hashCode(createTime); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketRegistry.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketRegistry.java new file mode 100644 index 00000000..116be82b --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/tickets/TicketRegistry.java @@ -0,0 +1,18 @@ +package cn.topiam.employee.protocol.cas.idp.tickets; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public interface TicketRegistry { + + void addTicket(Ticket ticket); + + Ticket getTicket(String id); + + T getTicket(String id, Class clazz); + + void updateTicket(Ticket ticket); + + void deleteTicket(String ticketId); +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/CasUtils.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/CasUtils.java new file mode 100644 index 00000000..aa9d1fbf --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/CasUtils.java @@ -0,0 +1,88 @@ +/* + * eiam-protocol-cas - Employee Identity and Access Management Program + * Copyright © 2020-2022 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 . + */ +package cn.topiam.employee.protocol.cas.idp.util; + +import cn.topiam.employee.application.ApplicationServiceLoader; +import cn.topiam.employee.common.repository.app.AppCasConfigRepository; +import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; +import org.springframework.context.ApplicationContext; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.core.session.SessionRegistry; + +import javax.xml.parsers.DocumentBuilder; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class CasUtils { + + public static > AppCasConfigRepository getAppCasConfigRepository(B builder) { + AppCasConfigRepository appRepository = builder + .getSharedObject(AppCasConfigRepository.class); + if (appRepository == null) { + appRepository = builder.getSharedObject(ApplicationContext.class) + .getBean(AppCasConfigRepository.class); + builder.setSharedObject(AppCasConfigRepository.class, appRepository); + } + return appRepository; + } + + public static > SessionRegistry getSessionRegistry(B builder) { + SessionRegistry sessionRegistry = builder.getSharedObject(SessionRegistry.class); + if (sessionRegistry == null) { + sessionRegistry = getBean(builder, SessionRegistry.class); + builder.setSharedObject(SessionRegistry.class, sessionRegistry); + } + return sessionRegistry; + } + + public static > CentralAuthenticationService getCentralAuthenticationService(B builder) { + CentralAuthenticationService authenticationService = builder + .getSharedObject(CentralAuthenticationService.class); + if (authenticationService == null) { + authenticationService = getBean(builder, CentralAuthenticationService.class); + builder.setSharedObject(CentralAuthenticationService.class, authenticationService); + } + return authenticationService; + } + + public static > DocumentBuilder getDocumentBuilder(B builder) { + DocumentBuilder documentBuilder = builder.getSharedObject(DocumentBuilder.class); + if (documentBuilder == null) { + documentBuilder = getBean(builder, DocumentBuilder.class); + builder.setSharedObject(DocumentBuilder.class, documentBuilder); + } + return documentBuilder; + } + + public static > ApplicationServiceLoader getApplicationServiceLoader(B builder) { + ApplicationServiceLoader applicationServiceLoader = builder + .getSharedObject(ApplicationServiceLoader.class); + if (applicationServiceLoader == null) { + applicationServiceLoader = getBean(builder, ApplicationServiceLoader.class); + builder.setSharedObject(ApplicationServiceLoader.class, applicationServiceLoader); + } + return applicationServiceLoader; + } + + public static , T> T getBean(B builder, Class type) { + return builder.getSharedObject(ApplicationContext.class).getBean(type); + } + +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/TicketUtils.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/TicketUtils.java new file mode 100644 index 00000000..6e7f9cf7 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/util/TicketUtils.java @@ -0,0 +1,71 @@ +package cn.topiam.employee.protocol.cas.idp.util; + +import org.apache.commons.lang3.StringUtils; + +import java.net.InetAddress; +import java.security.SecureRandom; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2022/12/29 16:25 + */ +public class TicketUtils { + private static final char[] PRINTABLE_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345679" + .toCharArray(); + + private static String getNewString() { + SecureRandom randomizer = new SecureRandom(); + byte[] random = new byte[20]; + randomizer.nextBytes(random); + return convertBytesToString(random); + } + + private static String convertBytesToString(byte[] random) { + char[] output = new char[random.length]; + IntStream.range(0, random.length).forEach((i) -> { + int index = Math.abs(random[i] % PRINTABLE_CHARACTERS.length); + output[i] = PRINTABLE_CHARACTERS[index]; + }); + return new String(output); + } + + private static AtomicLong count = new AtomicLong(0L); + + private static String getNextNumberAsString() { + return Long.toString(getNextValue()); + } + + private static long getNextValue() { + return count.compareAndSet(9223372036854775807L, 0L) ? 9223372036854775807L + : count.getAndIncrement(); + } + + private static String getSuffix(String suffix) { + if (StringUtils.isNotBlank(suffix)) { + return suffix; + } + return getCasServerHostName(); + } + + private static String getCasServerHostName() { + try { + String hostName = InetAddress.getLocalHost().getCanonicalHostName(); + int index = hostName.indexOf(46); + return index > 0 ? hostName.substring(0, index) : hostName; + } catch (Exception var2) { + throw new IllegalArgumentException("Host name could not be determined automatically.", + var2); + } + } + + private static String generateTicket(String prefix, String suffix) { + return prefix + "-" + getNextNumberAsString() + "-" + getNewString() + "-" + + getSuffix(suffix); + } + + public static String generateTicket(String prefix) { + return generateTicket(prefix, null); + } +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGenerator.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGenerator.java new file mode 100644 index 00000000..138ea146 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGenerator.java @@ -0,0 +1,17 @@ +package cn.topiam.employee.protocol.cas.idp.xml; + +import java.io.IOException; +import java.util.Map; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 21:22 + */ +public interface ResponseGenerator { + + void genFailedMessage(String serviceTicketId); + + void genSucceedMessage(String casUser, Map attributes); + + void sendMessage() throws IOException; +} diff --git a/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGeneratorImpl.java b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGeneratorImpl.java new file mode 100644 index 00000000..2d5ef098 --- /dev/null +++ b/eiam-protocol/eiam-protocol-cas/src/main/java/cn/topiam/employee/protocol/cas/idp/xml/ResponseGeneratorImpl.java @@ -0,0 +1,113 @@ +package cn.topiam.employee.protocol.cas.idp.xml; + +import org.dom4j.io.OutputFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.*; + +/** + * @author TopIAM + * Created by support@topiam.cn on 2023/1/2 20:23 + */ +public class ResponseGeneratorImpl implements ResponseGenerator { + + private final static Logger logger = LoggerFactory.getLogger(ResponseGeneratorImpl.class); + + private final HttpServletResponse response; + private final DocumentBuilder documentBuilder; + private String message; + + public ResponseGeneratorImpl(DocumentBuilder documentBuilder, HttpServletResponse response) { + this.response = response; + this.documentBuilder = documentBuilder; + } + + @Override + public void genFailedMessage(String serviceTicketId) { + Document document = documentBuilder.newDocument(); + Element serviceResponse = document.createElement(SERVICE_RESPONSE); + serviceResponse.setAttribute("xmlns:cas", SERVICE_ATTRIBUTES); + + Element failElement = document.createElement(AUTHENTICATION_FAILED); + failElement.setAttribute("code", INVALID_TICKET); + failElement.setTextContent(String.format("未能够识别出目标 '%s'票根", serviceTicketId)); + serviceResponse.appendChild(failElement); + document.appendChild(serviceResponse); + this.message = parseDocumentToString(serviceResponse); + } + + @Override + public void genSucceedMessage(String casUser, Map attributes) { + Document document = documentBuilder.newDocument(); + OutputFormat outputFormat = OutputFormat.createCompactFormat(); + outputFormat.setExpandEmptyElements(true); + Element serviceResponse = document.createElement(SERVICE_RESPONSE); + serviceResponse.setAttribute("xmlns:cas", SERVICE_ATTRIBUTES); + Element successElement = document.createElement(AUTHENTICATION_SUCCESS); + serviceResponse.appendChild(successElement); + Element userElement = document.createElement(CAS_USER); + userElement.setTextContent(casUser); + successElement.appendChild(userElement); + if (attributes.size() > 0) { + Element attributeElement = document.createElement(CAS_ATTRIBUTES); + for (Map.Entry entry : attributes.entrySet()) { + Object value = entry.getValue(); + if (value instanceof List) { + for (Object valueItem : (List) value) { + Element entryElement = document.createElement("cas:" + entry.getKey()); + entryElement.setTextContent(String.valueOf(valueItem)); + attributeElement.appendChild(entryElement); + } + } else { + Element entryElement = document.createElement("cas:" + entry.getKey()); + entryElement.setTextContent(String.valueOf(entry.getValue())); + attributeElement.appendChild(entryElement); + } + } + successElement.appendChild(attributeElement); + } + document.appendChild(serviceResponse); + this.message = parseDocumentToString(serviceResponse); + } + + @Override + public void sendMessage() throws IOException { + response.setContentType("text/xml;charset=UTF-8"); + PrintWriter out = response.getWriter(); + out.println(message); + out.flush(); + } + + private String parseDocumentToString(Element node) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TransformerFactory transFactory = TransformerFactory.newInstance(); + Transformer transformer = transFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");//序列化不保留标头 + DOMSource domSource = new DOMSource(node); + transformer.transform(domSource, new StreamResult(bos)); + return bos.toString(StandardCharsets.UTF_8); + } catch (TransformerException e) { + logger.error("xmlUtils failed to parseDocumentToString:" + e.getMessage(), e); + } + return ""; + } +}