mirror of https://gitee.com/topiam/eiam
⚡ 增加CAS协议支持
parent
72b36178dd
commit
16bed0aa80
|
@ -17,9 +17,14 @@
|
||||||
*/
|
*/
|
||||||
package cn.topiam.employee.application.cas;
|
package cn.topiam.employee.application.cas;
|
||||||
|
|
||||||
import cn.topiam.employee.application.ApplicationService;
|
import cn.topiam.employee.application.AbstractApplicationService;
|
||||||
import cn.topiam.employee.common.repository.app.AppCertRepository;
|
import cn.topiam.employee.application.CasApplicationService;
|
||||||
import cn.topiam.employee.common.repository.app.AppRepository;
|
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 应用配置
|
* CAS 应用配置
|
||||||
|
@ -27,20 +32,52 @@ import cn.topiam.employee.common.repository.app.AppRepository;
|
||||||
* @author TopIAM
|
* @author TopIAM
|
||||||
* Created by support@topiam.cn on 2022/8/23 20:58
|
* 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
|
* ApplicationRepository
|
||||||
*/
|
*/
|
||||||
protected final AppRepository appRepository;
|
protected final AppRepository appRepository;
|
||||||
|
|
||||||
|
protected final AppCasConfigRepository appCasConfigRepository;
|
||||||
|
|
||||||
protected AbstractCasApplicationService(AppCertRepository appCertRepository,
|
protected AbstractCasApplicationService(AppCertRepository appCertRepository,
|
||||||
AppRepository appRepository) {
|
AppAccountRepository appAccountRepository,
|
||||||
this.appCertRepository = appCertRepository;
|
AppAccessPolicyRepository appAccessPolicyRepository,
|
||||||
|
AppRepository appRepository,
|
||||||
|
AppCasConfigRepository appCasConfigRepository) {
|
||||||
|
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository);
|
||||||
this.appRepository = 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,34 @@
|
||||||
*/
|
*/
|
||||||
package cn.topiam.employee.application.cas;
|
package cn.topiam.employee.application.cas;
|
||||||
|
|
||||||
import java.util.List;
|
import cn.topiam.employee.application.cas.model.AppCasStandardConfigGetResult;
|
||||||
import java.util.Map;
|
import cn.topiam.employee.application.cas.model.AppCasStandardSaveConfigParam;
|
||||||
|
import cn.topiam.employee.application.exception.AppNotExistException;
|
||||||
import org.springframework.stereotype.Component;
|
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.AppProtocol;
|
||||||
import cn.topiam.employee.common.enums.app.AppType;
|
import cn.topiam.employee.common.enums.app.AppType;
|
||||||
import cn.topiam.employee.common.repository.app.AppCertRepository;
|
import cn.topiam.employee.common.enums.app.AuthorizationType;
|
||||||
import cn.topiam.employee.common.repository.app.AppRepository;
|
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 用户应用
|
* Cas 用户应用
|
||||||
|
@ -35,6 +54,23 @@ import cn.topiam.employee.common.repository.app.AppRepository;
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class CasStandardApplicationServiceImpl extends AbstractCasApplicationService {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新应用配置
|
* 更新应用配置
|
||||||
|
@ -44,6 +80,46 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void saveConfig(String appId, Map<String, Object> config) {
|
public void saveConfig(String appId, Map<String, Object> 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<AppCasStandardSaveConfigParam> validationResult = ValidationHelp
|
||||||
|
.validateEntity(model);
|
||||||
|
if (validationResult.isHasErrors()) {
|
||||||
|
throw new ConstraintViolationException(validationResult.getConstraintViolations());
|
||||||
|
}
|
||||||
|
|
||||||
|
//1、修改基本信息
|
||||||
|
Optional<AppEntity> 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<AppCasConfigEntity> 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
|
@Override
|
||||||
public Object getConfig(String appId) {
|
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
|
@Override
|
||||||
public List<Map> getFormSchema() {
|
public List<Map> getFormSchema() {
|
||||||
return null;
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,21 +223,27 @@ public class CasStandardApplicationServiceImpl extends AbstractCasApplicationSer
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String create(String name, String remark) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -17,15 +17,6 @@
|
||||||
*/
|
*/
|
||||||
package cn.topiam.employee.application;
|
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.entity.app.AppCertEntity;
|
||||||
import cn.topiam.employee.common.enums.app.AppCertUsingType;
|
import cn.topiam.employee.common.enums.app.AppCertUsingType;
|
||||||
import cn.topiam.employee.common.repository.app.AppAccessPolicyRepository;
|
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.exception.TopIamException;
|
||||||
import cn.topiam.employee.support.util.CertUtils;
|
import cn.topiam.employee.support.util.CertUtils;
|
||||||
import cn.topiam.employee.support.util.RsaUtils;
|
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.encodePem;
|
||||||
import static cn.topiam.employee.support.util.CertUtils.getX500Name;
|
import static cn.topiam.employee.support.util.CertUtils.getX500Name;
|
||||||
import static cn.topiam.employee.support.util.RsaUtils.getKeys;
|
import static cn.topiam.employee.support.util.RsaUtils.getKeys;
|
||||||
|
@ -47,6 +50,7 @@ import static cn.topiam.employee.support.util.RsaUtils.getKeys;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractApplicationService implements ApplicationService {
|
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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建证书
|
* 创建证书
|
||||||
|
@ -124,6 +128,11 @@ public abstract class AbstractApplicationService implements ApplicationService {
|
||||||
*/
|
*/
|
||||||
protected final AppRepository appRepository;
|
protected final AppRepository appRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IdGenerator
|
||||||
|
*/
|
||||||
|
protected final IdGenerator idGenerator;
|
||||||
|
|
||||||
protected AbstractApplicationService(AppCertRepository appCertRepository,
|
protected AbstractApplicationService(AppCertRepository appCertRepository,
|
||||||
AppAccountRepository appAccountRepository,
|
AppAccountRepository appAccountRepository,
|
||||||
AppAccessPolicyRepository appAccessPolicyRepository,
|
AppAccessPolicyRepository appAccessPolicyRepository,
|
||||||
|
@ -132,5 +141,6 @@ public abstract class AbstractApplicationService implements ApplicationService {
|
||||||
this.appAccountRepository = appAccountRepository;
|
this.appAccountRepository = appAccountRepository;
|
||||||
this.appAccessPolicyRepository = appAccessPolicyRepository;
|
this.appAccessPolicyRepository = appAccessPolicyRepository;
|
||||||
this.appRepository = appRepository;
|
this.appRepository = appRepository;
|
||||||
|
this.idGenerator = new AlternativeJdkIdGenerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -26,9 +26,7 @@ import org.mapstruct.Mapping;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.AlternativeJdkIdGenerator;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.IdGenerator;
|
|
||||||
|
|
||||||
import cn.topiam.employee.application.AbstractApplicationService;
|
import cn.topiam.employee.application.AbstractApplicationService;
|
||||||
import cn.topiam.employee.application.Saml2ApplicationService;
|
import cn.topiam.employee.application.Saml2ApplicationService;
|
||||||
|
@ -136,11 +134,6 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService
|
||||||
*/
|
*/
|
||||||
protected final AppSaml2ConfigRepository appSaml2ConfigRepository;
|
protected final AppSaml2ConfigRepository appSaml2ConfigRepository;
|
||||||
|
|
||||||
/**
|
|
||||||
* IdGenerator
|
|
||||||
*/
|
|
||||||
protected final IdGenerator idGenerator;
|
|
||||||
|
|
||||||
protected AbstractSamlAppService(AppCertRepository appCertRepository,
|
protected AbstractSamlAppService(AppCertRepository appCertRepository,
|
||||||
AppAccountRepository appAccountRepository,
|
AppAccountRepository appAccountRepository,
|
||||||
AppAccessPolicyRepository appAccessPolicyRepository,
|
AppAccessPolicyRepository appAccessPolicyRepository,
|
||||||
|
@ -148,7 +141,6 @@ public abstract class AbstractSamlAppService extends AbstractApplicationService
|
||||||
AppSaml2ConfigRepository appSaml2ConfigRepository) {
|
AppSaml2ConfigRepository appSaml2ConfigRepository) {
|
||||||
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository);
|
super(appCertRepository, appAccountRepository, appAccessPolicyRepository, appRepository);
|
||||||
this.appSaml2ConfigRepository = appSaml2ConfigRepository;
|
this.appSaml2ConfigRepository = appSaml2ConfigRepository;
|
||||||
this.idGenerator = new AlternativeJdkIdGenerator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mapper(componentModel = "spring")
|
@Mapper(componentModel = "spring")
|
||||||
|
|
|
@ -71,7 +71,6 @@ public class Saml2StandardApplicationServiceImpl extends AbstractSamlAppService
|
||||||
public void saveConfig(String appId, Map<String, Object> config) {
|
public void saveConfig(String appId, Map<String, Object> config) {
|
||||||
AppSaml2StandardSaveConfigParam model;
|
AppSaml2StandardSaveConfigParam model;
|
||||||
try {
|
try {
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
String value = mapper.writeValueAsString(config);
|
String value = mapper.writeValueAsString(config);
|
||||||
// 指定序列化输入的类型
|
// 指定序列化输入的类型
|
||||||
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
|
mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
|
|
@ -35,6 +35,7 @@ public final class ConfigBeanNameConstants {
|
||||||
public static final String SOCIAL_SECURITY_FILTER_CHAIN = "socialSecurityFilterChain";
|
public static final String SOCIAL_SECURITY_FILTER_CHAIN = "socialSecurityFilterChain";
|
||||||
public static final String SAML2_PROTOCOL_SECURITY_FILTER_CHAIN = "saml2ProtocolSecurityFilterChain";
|
public static final String SAML2_PROTOCOL_SECURITY_FILTER_CHAIN = "saml2ProtocolSecurityFilterChain";
|
||||||
public static final String OIDC_PROTOCOL_SECURITY_FILTER_CHAIN = "oidcProtocolSecurityFilterChain";
|
public static final String OIDC_PROTOCOL_SECURITY_FILTER_CHAIN = "oidcProtocolSecurityFilterChain";
|
||||||
|
public static final String CAS_PROTOCOL_SECURITY_FILTER_CHAIN = "casProtocolSecurityFilterChain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认密码策略管理器
|
* 默认密码策略管理器
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
package cn.topiam.employee.common.constants;
|
package cn.topiam.employee.common.constants;
|
||||||
|
|
||||||
import lombok.Data;
|
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.AppConstants.APP_CACHE_NAME_PREFIX;
|
||||||
import static cn.topiam.employee.common.constants.AuthorizeConstants.AUTHORIZE_PATH;
|
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 常量
|
* Saml 常量
|
||||||
|
@ -49,6 +49,11 @@ public final class ProtocolConstants {
|
||||||
*/
|
*/
|
||||||
public static final String SAML2_CONFIG_CACHE_NAME = APP_CACHE_NAME_PREFIX + "saml";
|
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 配置缓存名称
|
* OIDC 配置缓存名称
|
||||||
*/
|
*/
|
||||||
|
@ -144,4 +149,27 @@ public final class ProtocolConstants {
|
||||||
public static final String SAML_SSO_PATH = SAML2_AUTHORIZE_BASE_PATH + "/sso";
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<Long> {
|
||||||
|
/**
|
||||||
|
* APP ID
|
||||||
|
*/
|
||||||
|
@Column(name = "app_id")
|
||||||
|
private Long appId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SP 接受回调地址
|
||||||
|
*/
|
||||||
|
@Column(name = "sp_callback_url")
|
||||||
|
private String spCallbackUrl;
|
||||||
|
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<AppCasConfigEntity, Long>,
|
||||||
|
QuerydslPredicateExecutor<AppCasConfigEntity>,
|
||||||
|
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 <S> {@link S}
|
||||||
|
* @return {@link AppCasConfigEntity}
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
@CacheEvict(allEntries = true)
|
||||||
|
<S extends AppCasConfigEntity> S save(@NotNull S entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据应用ID获取配置
|
||||||
|
*
|
||||||
|
* @param appId {@link Long}
|
||||||
|
* @return {@link AppCasConfigEntity}
|
||||||
|
*/
|
||||||
|
Optional<AppCasConfigEntity> findByAppId(Long appId);
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<AppCasConfigPO> {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog
|
||||||
|
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd
|
||||||
|
http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/pro/liquibase-pro-4.3.xsd">
|
||||||
|
<!--init-->
|
||||||
|
<changeSet author="TopIAM" id="1653202564123-0">
|
||||||
|
<createTable remarks="CAS 应用配置" tableName="app_cas_config">
|
||||||
|
<column name="id_" type="BIGINT" remarks="主键ID">
|
||||||
|
<constraints nullable="false" primaryKey="true"/>
|
||||||
|
</column>
|
||||||
|
<column name="app_id" remarks="应用ID" type="BIGINT">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="sp_callback_url" remarks="客户端接受回调地址" type="VARCHAR(128)"/>
|
||||||
|
<column name="additional_config" remarks="额外配置" type="JSON"/>
|
||||||
|
<column name="create_by" remarks="创建者" type="VARCHAR(64)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column defaultValueComputed="CURRENT_TIMESTAMP" name="create_time" remarks="创建时间" type="datetime">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="update_by" remarks="修改者" type="VARCHAR(64)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column defaultValueComputed="CURRENT_TIMESTAMP" name="update_time" remarks="修改时间" type="datetime">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="remark_" remarks="备注" type="TEXT"/>
|
||||||
|
</createTable>
|
||||||
|
</changeSet>
|
||||||
|
</databaseChangeLog>
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -105,6 +105,7 @@ import cn.topiam.employee.portal.listener.PortalAuthenticationSuccessEventListen
|
||||||
import cn.topiam.employee.portal.listener.PortalLogoutSuccessEventListener;
|
import cn.topiam.employee.portal.listener.PortalLogoutSuccessEventListener;
|
||||||
import cn.topiam.employee.portal.listener.PortalSessionInformationExpiredStrategy;
|
import cn.topiam.employee.portal.listener.PortalSessionInformationExpiredStrategy;
|
||||||
import cn.topiam.employee.portal.mfa.MfaAuthenticationConfigurer;
|
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.authentication.EiamOAuth2AuthorizationService;
|
||||||
import cn.topiam.employee.protocol.oidc.repository.OidcConfigRegisteredClientRepository;
|
import cn.topiam.employee.protocol.oidc.repository.OidcConfigRegisteredClientRepository;
|
||||||
import cn.topiam.employee.protocol.oidc.token.ApplicationOpaqueTokenIntrospector;
|
import cn.topiam.employee.protocol.oidc.token.ApplicationOpaqueTokenIntrospector;
|
||||||
|
@ -230,6 +231,36 @@ public class PortalSecurityConfiguration {
|
||||||
return http.build();
|
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<HttpSecurity> 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 协议
|
* SAML 协议
|
||||||
*
|
*
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<B extends HttpSecurityBuilder<B>>
|
||||||
|
extends AbstractHttpConfigurer<CasIdpConfigurer<B>, 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<RequestMatcher> requestMatchers = new ArrayList<>();
|
||||||
|
requestMatchers.add(CasIdpSingleSignOnEndpointFilter.getRequestMatcher());
|
||||||
|
requestMatchers.add(CasIdpValidateEndpointFilter.getRequestMatcher());
|
||||||
|
return new OrRequestMatcher(requestMatchers);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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 extends Ticket> T getTicket(String id, Class<T> clazz);
|
||||||
|
|
||||||
|
ServiceTicket validateServiceTicket(String id, String service);
|
||||||
|
|
||||||
|
void destroyTicketGrantingTicket(String var1);
|
||||||
|
}
|
|
@ -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 extends Ticket> T getTicket(String id, Class<T> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
public CentralCacheService(RedisTemplate<Object, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
||||||
|
}
|
|
@ -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<SessionInformation> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<RequestMatcher> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String, String> variables = appAuthorizePathRequestMatcher.matcher(request)
|
||||||
|
.getVariables();
|
||||||
|
String appCode = variables.get(APP_CODE);
|
||||||
|
AppCasConfigPO configPo = appCasConfigRepository.findByAppCode(appCode);
|
||||||
|
if (Objects.isNull(configPo)) {
|
||||||
|
throw new AppNotExistException();
|
||||||
|
}
|
||||||
|
|
||||||
|
//封装上下文内容
|
||||||
|
Map<String, Object> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends TicketFactory> T get(final Class<? extends Ticket> clazz) {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Object> 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 extends TicketFactory> T get(final Class<? extends Ticket> clazz) {
|
||||||
|
return (T) this.factoryMap.get(clazz.getCanonicalName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends TicketFactory> T get(final Class<? extends Ticket> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends Ticket> T getTicket(final String id, final Class<T> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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 extends TicketFactory> T get(Class<? extends Ticket> clazz);
|
||||||
|
}
|
|
@ -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
|
||||||
|
* <p>
|
||||||
|
* 其实TGT可以理解为用户登陆的session,包含了用户的信息
|
||||||
|
*/
|
||||||
|
public interface TicketGrantingTicket extends Ticket {
|
||||||
|
ServiceTicket grantServiceTicket(String service);
|
||||||
|
|
||||||
|
UserDetails getUserDetails();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 extends Ticket> T getTicket(String id, Class<T> clazz);
|
||||||
|
|
||||||
|
void updateTicket(Ticket ticket);
|
||||||
|
|
||||||
|
void deleteTicket(String ticketId);
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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 <B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, Class<T> type) {
|
||||||
|
return builder.getSharedObject(ApplicationContext.class).getBean(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Object> attributes);
|
||||||
|
|
||||||
|
void sendMessage() throws IOException;
|
||||||
|
}
|
|
@ -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<String, Object> 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<String, Object> 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 "";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue