问题说明
最近项目需要解决CAS认证登陆的内外网访问的问题,当使用通用的CAS统一认证服务时,由于WEB应用工程中application.yml配置的CAS地址是固定的,在项目启动完成后,相关的对象和过滤器都创建好了,此时的地址只能是内网和外网中的其中一个,另外一个网络并没有进行cas的配置,则无法完成sso单点登录。由于项目的本身需要,必须要同时内外网都能访问。
出于这个目的,cas的配置必须根据访问的IP地址,判断请求是内网还是外网,调用不同环境下的配置参数,实现内外网的动态切换,由此看了下源码后,发现在创建bean的时候,指定的cas参数都是不能修改的,经过反复研究和调试,本文采用重写cas过滤器的方式,在不改动任何源码的方式下,实现了cas认证登录内外网都支持的需求。
解决方案如下:
1. 配置说明
#登录地址
#自身平台地址(服务名称,效验ticket需要)
这两个参数须在外网配置对应的访问地址
cas:
#过滤路径
sign-out-filters: /cas/*
auth-filters: /cas/*
validate-filters: /cas/*
request-wrapper-filters: /cas/*
#内网环境
#登录地址
cas-server-login-url: https://10.1.1.1:442/portal/cas/login
#自身平台地址(服务名称,效验ticket需要)
server-name: http://10.1.1.50:442
#效验的前缀(目前没发现需要指定外网)
cas-server-url-prefix: https://10.1.1.1:442/bic/ssoService
#外网环境
#登录地址 == 等价于 内网中的cas-server-login-url
cas-server-login-url-out: https://外网:442/portal/cas/login
#外网自身平台地址(服务名称,效验ticket需要) == 等价于 内网中的server-name
server-name-index: https://外网:442
#配置不需要过cas认证的url规则
ignore-pattern: /js/*|/images/*|/api*|/css/*
#要过滤的请求
url-Patterns: /cas/*
loginType: true # 动态开启 true(启用cas) false(不启用) 单点登录
config类
@Configuration
@ConditionalOnProperty(value = "spring.cas.loginType", havingValue = "true")
public class AppConfig {
@Value("${spring.cas.validate-filters}")
private String validateFilters;
@Value("${spring.cas.sign-out-filters}")
private String signOutFilters;
@Value("${spring.cas.auth-filters}")
private String authFilters;
@Value("${spring.cas.request-wrapper-filters}")
private String requestWrapperFilters;
@Value("${spring.cas.cas-server-url-prefix}")
private String casServerUrlPrefix;
@Value("${spring.cas.cas-server-login-url}")
private String casServerLoginUrl;
//外网的
@Value("${spring.cas.cas-server-login-url-out}")
private String outCasServerLoginUrl;
@Value("${spring.cas.server-name}")
private String serverName;
//外网自身
@Value("${spring.cas.server-name-out}")
private String outServerName;
@Value("${spring.cas.ignore-pattern}")
private String ignorePattern;
//要过滤的请求
@Value("${spring.cas.url-patterns}")
private String urlPatterns;
2. 根据源码新建四个过滤器
2.1. AuthenticationFilter
AuthenticationFilter过滤器为cas客户端核心过滤器,主要涉及到认证流程,判断是否有ticket,如果没有则根据配置的信息来决定将跳到什么地方,有的话就去效验ticket,去Validation相关过滤器验证。
package com.zhaolin.common.filter;
import com.zhaolin.common.utils.ServletUtils;
import com.zhaolin.common.utils.ip.IpUtils;
import org.jasig.cas.client.authentication.*;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @description: cas自定义过滤器
* @author: tst
* @create: 2022-03-29 09:37
**/
@Component
public class AuthenticationFilter extends AbstractCasFilter {
/**
* The URL to the CAS Server login.
*/
private String casServerLoginUrl;
//外网
private String outCasServerLoginUrl;
//外网自生地址
private String outServerName;
/**
* Whether to send the renew request or not.
*/
private boolean renew = false;
/**
* Whether to send the gateway request or not.
*/
private boolean gateway = false;
/**
* The method used by the CAS server to send the user back to the application.
*/
private String method;
private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;
private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES =
new HashMap<String, Class<? extends UrlPatternMatcherStrategy>>();
static {
PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("FULL_REGEX", EntireRegionRegexUrlPatternMatcherStrategy.class);
PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);
}
public AuthenticationFilter(String outCasServerLoginUrl,String outServerName) {
this(Protocol.CAS2);
this.outCasServerLoginUrl = outCasServerLoginUrl;
this.outServerName = outServerName;
}
public AuthenticationFilter() {
this(Protocol.CAS2);
}
protected AuthenticationFilter(final Protocol protocol) {
super(protocol);
}
@Override
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
if (!isIgnoreInitConfiguration()) {
super.initInternal(filterConfig);
final String loginUrl = getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL);
if (loginUrl != null) {
setCasServerLoginUrl(loginUrl);
} else {
setCasServerUrlPrefix(getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX));
}
setRenew(getBoolean(ConfigurationKeys.RENEW));
setGateway(getBoolean(ConfigurationKeys.GATEWAY));
setMethod(getString(ConfigurationKeys.METHOD));
final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
if (ignorePattern != null) {
final Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
if (ignoreUrlMatcherClass != null) {
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
} else {
try {
logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
} catch (final IllegalArgumentException e) {
logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
}
}
if (this.ignoreUrlPatternMatcherStrategyClass != null) {
this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
}
}
final Class<? extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
if (gatewayStorageClass != null) {
setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
}
final Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
if (authenticationRedirectStrategyClass != null) {
this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
}
}
}
@Override
public void init() {
super.init();
final String message = String.format(
"one of %s and %s must not be null.",
ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(),
ConfigurationKeys.CAS_SERVER_URL_PREFIX.getName());
CommonUtils.assertNotNull(this.casServerLoginUrl, message);
}
@Override
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取id地址
String ipAddr = IpUtils.getIpAddr(request);
// true表示内网
boolean booleanInternal = IpUtils.internalIp(ipAddr);
System.out.println(ipAddr + "是否内网:" + booleanInternal);
//service前面
String trueCaseServerLoginUrl = "";
//service后面
String serviceUrl = "";
if(booleanInternal){//内网
trueCaseServerLoginUrl = casServerLoginUrl;
serviceUrl = constructServiceUrl(request, response);
}else{
trueCaseServerLoginUrl = outCasServerLoginUrl;
//外网
serviceUrl = outServerName + request.getRequestURI();
}
if (isRequestUrlExcluded(request)) {
logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
return;
}
final HttpSession session = request.getSession(false);
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
// final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
filterChain.doFilter(request, response);
return;
}
final String modifiedServiceUrl;
logger.debug("no ticket and no assertion found");
if (this.gateway) {
logger.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
logger.debug("Constructed service url: {}", modifiedServiceUrl);
// final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
// getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(trueCaseServerLoginUrl,
getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);
logger.debug("redirecting to \"{}\"", urlToRedirectTo);
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
}
public final void setRenew(final boolean renew) {
this.renew = renew;
}
public final void setGateway(final boolean gateway) {
this.gateway = gateway;
}
public void setMethod(final String method) {
this.method = method;
}
public final void setCasServerUrlPrefix(final String casServerUrlPrefix) {
setCasServerLoginUrl(CommonUtils.addTrailingSlash(casServerUrlPrefix) + "login");
}
public final void setCasServerLoginUrl(final String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
this.gatewayStorage = gatewayStorage;
}
private boolean isRequestUrlExcluded(final HttpServletRequest request) {
if (this.ignoreUrlPatternMatcherStrategyClass == null) {
return false;
}
final StringBuffer urlBuffer = request.getRequestURL();
if (request.getQueryString() != null) {
urlBuffer.append("?").append(request.getQueryString());
}
final String requestUri = urlBuffer.toString();
return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
}
public final void setIgnoreUrlPatternMatcherStrategyClass(
final UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass) {
this.ignoreUrlPatternMatcherStrategyClass = ignoreUrlPatternMatcherStrategyClass;
}
}
2.2 Cas20ProxyReceivingTicketValidationFilter
Cas20ProxyReceivingTicketValidationFilter: ticket效验需要,这个过滤器是可以直接创建的,我们要在这里判断内外网,获取#自身平台地址(服务名称,效验ticket需要)。
package com.zhaolin.common.filter;
import com.zhaolin.common.utils.ip.IpUtils;
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.proxy.*;
import org.jasig.cas.client.ssl.HttpURLConnectionFactory;
import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.PrivateKeyUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.PrivateKey;
import java.util.*;
import static org.jasig.cas.client.configuration.ConfigurationKeys.*;
import static org.jasig.cas.client.configuration.ConfigurationKeys.PRIVATE_KEY_ALGORITHM;
import static org.jasig.cas.client.configuration.ConfigurationKeys.PRIVATE_KEY_PATH;
/**
* @description: 自定义票据验证
* @author: tst
* @create: 2022-03-30 18:24
**/
public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketValidationFilter {
private static final String[] RESERVED_INIT_PARAMS = new String[]{ARTIFACT_PARAMETER_NAME.getName(), SERVER_NAME.getName(), SERVICE.getName(), RENEW.getName(), LOGOUT_PARAMETER_NAME.getName(),
ARTIFACT_PARAMETER_OVER_POST.getName(), EAGERLY_CREATE_SESSIONS.getName(), ENCODE_SERVICE_URL.getName(), SSL_CONFIG_FILE.getName(), ROLE_ATTRIBUTE.getName(), IGNORE_CASE.getName(),
CAS_SERVER_LOGIN_URL.getName(), GATEWAY.getName(), AUTHENTICATION_REDIRECT_STRATEGY_CLASS.getName(), GATEWAY_STORAGE_CLASS.getName(), CAS_SERVER_URL_PREFIX.getName(), ENCODING.getName(),
TOLERANCE.getName(), IGNORE_PATTERN.getName(), IGNORE_URL_PATTERN_TYPE.getName(), HOSTNAME_VERIFIER.getName(), HOSTNAME_VERIFIER_CONFIG.getName(),
EXCEPTION_ON_VALIDATION_FAILURE.getName(), REDIRECT_AFTER_VALIDATION.getName(), USE_SESSION.getName(), SECRET_KEY.getName(), CIPHER_ALGORITHM.getName(), PROXY_RECEPTOR_URL.getName(),
PROXY_GRANTING_TICKET_STORAGE_CLASS.getName(), MILLIS_BETWEEN_CLEAN_UPS.getName(), ACCEPT_ANY_PROXY.getName(), ALLOWED_PROXY_CHAINS.getName(), TICKET_VALIDATOR_CLASS.getName(),
PROXY_CALLBACK_URL.getName(), RELAY_STATE_PARAMETER_NAME.getName(), METHOD.getName(), PRIVATE_KEY_PATH.getName(), PRIVATE_KEY_ALGORITHM.getName()
};
/**
* The URL to send to the CAS server as the URL that will process proxying requests on the CAS client.
*/
private String proxyReceptorUrl;
private Timer timer;
private TimerTask timerTask;
private int millisBetweenCleanUps;
protected Class<? extends Cas20ServiceTicketValidator> defaultServiceTicketValidatorClass;
protected Class<? extends Cas20ProxyTicketValidator> defaultProxyTicketValidatorClass;
private PrivateKey privateKey;
//内网平台
private String tstServerName;
//外网平台
private String tstServerNameOut;
/**
* Storage location of ProxyGrantingTickets and Proxy Ticket IOUs.
*/
private ProxyGrantingTicketStorage proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl();
public Cas20ProxyReceivingTicketValidationFilter() {
this(Protocol.CAS2);
this.defaultServiceTicketValidatorClass = Cas20ServiceTicketValidator.class;
this.defaultProxyTicketValidatorClass = Cas20ProxyTicketValidator.class;
}
//新增构造方法
public Cas20ProxyReceivingTicketValidationFilter(String tstServerName,String tstServerNameOut) {
this(Protocol.CAS2);
this.defaultServiceTicketValidatorClass = Cas20ServiceTicketValidator.class;
this.defaultProxyTicketValidatorClass = Cas20ProxyTicketValidator.class;
this.tstServerName = tstServerName;
this.tstServerNameOut = tstServerNameOut;
}
protected Cas20ProxyReceivingTicketValidationFilter(final Protocol protocol) {
super(protocol);
}
@Override
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
setProxyReceptorUrl(getString(ConfigurationKeys.PROXY_RECEPTOR_URL));
final Class<? extends ProxyGrantingTicketStorage> proxyGrantingTicketStorageClass = getClass(ConfigurationKeys.PROXY_GRANTING_TICKET_STORAGE_CLASS);
if (proxyGrantingTicketStorageClass != null) {
this.proxyGrantingTicketStorage = ReflectUtils.newInstance(proxyGrantingTicketStorageClass);
if (this.proxyGrantingTicketStorage instanceof AbstractEncryptedProxyGrantingTicketStorageImpl) {
final AbstractEncryptedProxyGrantingTicketStorageImpl p = (AbstractEncryptedProxyGrantingTicketStorageImpl) this.proxyGrantingTicketStorage;
final String cipherAlgorithm = getString(ConfigurationKeys.CIPHER_ALGORITHM);
final String secretKey = getString(ConfigurationKeys.SECRET_KEY);
p.setCipherAlgorithm(cipherAlgorithm);
try {
if (secretKey != null) {
p.setSecretKey(secretKey);
}
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}
this.millisBetweenCleanUps = getInt(ConfigurationKeys.MILLIS_BETWEEN_CLEAN_UPS);
this.privateKey = buildPrivateKey(getString(PRIVATE_KEY_PATH), getString(PRIVATE_KEY_ALGORITHM));
super.initInternal(filterConfig);
}
@Override
public void init() {
super.init();
CommonUtils.assertNotNull(this.proxyGrantingTicketStorage, "proxyGrantingTicketStorage cannot be null.");
if (this.timer == null) {
this.timer = new Timer(true);
}
if (this.timerTask == null) {
this.timerTask = new CleanUpTimerTask(this.proxyGrantingTicketStorage);
}
this.timer.schedule(this.timerTask, this.millisBetweenCleanUps, this.millisBetweenCleanUps);
}
private <T> T createNewTicketValidator(final Class<? extends Cas20ServiceTicketValidator> ticketValidatorClass, final String casServerUrlPrefix,
final Class<T> clazz) {
if (ticketValidatorClass == null) {
return ReflectUtils.newInstance(clazz, casServerUrlPrefix);
}
return (T) ReflectUtils.newInstance(ticketValidatorClass, casServerUrlPrefix);
}
public static PrivateKey buildPrivateKey(final String keyPath, final String keyAlgorithm) {
if (keyPath != null) {
return PrivateKeyUtils.createKey(keyPath, keyAlgorithm);
}
return null;
}
/**
* Constructs a Cas20ServiceTicketValidator or a Cas20ProxyTicketValidator based on supplied parameters.
*
* @param filterConfig the Filter Configuration object.
* @return a fully constructed TicketValidator.
*/
@Override
protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) {
final boolean allowAnyProxy = getBoolean(ConfigurationKeys.ACCEPT_ANY_PROXY);
final String allowedProxyChains = getString(ConfigurationKeys.ALLOWED_PROXY_CHAINS);
final String casServerUrlPrefix = getString(ConfigurationKeys.CAS_SERVER_URL_PREFIX);
final Class<? extends Cas20ServiceTicketValidator> ticketValidatorClass = getClass(ConfigurationKeys.TICKET_VALIDATOR_CLASS);
final Cas20ServiceTicketValidator validator;
if (allowAnyProxy || CommonUtils.isNotBlank(allowedProxyChains)) {
final Cas20ProxyTicketValidator v = createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
this.defaultProxyTicketValidatorClass);
v.setAcceptAnyProxy(allowAnyProxy);
v.setAllowedProxyChains(CommonUtils.createProxyList(allowedProxyChains));
validator = v;
} else {
validator = createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
this.defaultServiceTicketValidatorClass);
}
validator.setProxyCallbackUrl(getString(ConfigurationKeys.PROXY_CALLBACK_URL));
validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage);
final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(),
getSSLConfig());
validator.setURLConnectionFactory(factory);
validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix, getString(ConfigurationKeys.ENCODING), factory));
validator.setRenew(getBoolean(ConfigurationKeys.RENEW));
validator.setEncoding(getString(ConfigurationKeys.ENCODING));
final Map<String, String> additionalParameters = new HashMap<String, String>();
final List<String> params = Arrays.asList(RESERVED_INIT_PARAMS);
for (final Enumeration<?> e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {
final String s = (String) e.nextElement();
if (!params.contains(s)) {
additionalParameters.put(s, filterConfig.getInitParameter(s));
}
}
validator.setPrivateKey(this.privateKey);
validator.setCustomParameters(additionalParameters);
return validator;
}
@Override
public void destroy() {
super.destroy();
this.timer.cancel();
}
/**
* This processes the ProxyReceptor request before the ticket validation code executes.
*/
@Override
protected final boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final String requestUri = request.getRequestURI();
if (CommonUtils.isEmpty(this.proxyReceptorUrl) || !requestUri.endsWith(this.proxyReceptorUrl)) {
return true;
}
try {
CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
} catch (final RuntimeException e) {
logger.error(e.getMessage(), e);
throw e;
}
return false;
}
/**
* @Description: 动态获取serverName,验证要判断
* @Param:
* @return:
* @Author: tst
* @Date: 2022-03-31
*/
protected String getServerName(HttpServletRequest request) {
//判断id地址
String ipAddr = IpUtils.getIpAddr(request);
//是否内网
boolean isInternal = IpUtils.internalIp(ipAddr);
String serverName = "";
if(isInternal){//内网
serverName = tstServerName;
}else{//外网
serverName = tstServerNameOut;
}
return serverName;
}
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
this.proxyReceptorUrl = proxyReceptorUrl;
}
public void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage storage) {
this.proxyGrantingTicketStorage = storage;
}
public void setTimer(final Timer timer) {
this.timer = timer;
}
public void setTimerTask(final TimerTask timerTask) {
this.timerTask = timerTask;
}
public void setMillisBetweenCleanUps(final int millisBetweenCleanUps) {
this.millisBetweenCleanUps = millisBetweenCleanUps;
}
}
2.3 AbstractTicketValidationFilter
AbstractTicketValidationFilter过滤器主要调用Cas20ProxyReceivingTicketValidationFilter中的getServerName(request)方法,获取#自身平台地址(服务名称,效验ticket需要)这个参数(serverName),然后调用新重载的方法constructServiceUrl,传入动态的serverName。
如下图所示:
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import com.zhaolin.common.filter.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import javax.net.ssl.HostnameVerifier;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* @description: 自定义验证票据过滤器
* @author: tst
* @create: 2022-03-30 21:00
**/
public class AbstractTicketValidationFilter extends AbstractCasFilter {
/** The TicketValidator we will use to validate tickets. */
private TicketValidator ticketValidator;
/**
* Specify whether the filter should redirect the user agent after a
* successful validation to remove the ticket parameter from the query
* string.
*/
private boolean redirectAfterValidation = true;
/** Determines whether an exception is thrown when there is a ticket validation failure. */
private boolean exceptionOnValidationFailure = false;
/**
* Specify whether the Assertion should be stored in a session
* attribute {@link AbstractCasFilter#CONST_CAS_ASSERTION}.
*/
private boolean useSession = true;
protected AbstractTicketValidationFilter(final Protocol protocol) {
super(protocol);
}
/**
* Template method to return the appropriate validator.
*
* @param filterConfig the FilterConfiguration that may be needed to construct a validator.
* @return the ticket validator.
*/
protected TicketValidator getTicketValidator(final FilterConfig filterConfig) {
return this.ticketValidator;
}
/**
* Gets the ssl config to use for HTTPS connections
* if one is configured for this filter.
* @return Properties that can contains key/trust info for Client Side Certificates
*/
protected Properties getSSLConfig() {
final Properties properties = new Properties();
final String fileName = getString(ConfigurationKeys.SSL_CONFIG_FILE);
if (fileName != null) {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
properties.load(fis);
logger.trace("Loaded {} entries from {}", properties.size(), fileName);
} catch (final IOException ioe) {
logger.error(ioe.getMessage(), ioe);
} finally {
CommonUtils.closeQuietly(fis);
}
}
return properties;
}
/**
* Gets the configured {@link HostnameVerifier} to use for HTTPS connections
* if one is configured for this filter.
* @return Instance of specified host name verifier or null if none specified.
*/
protected HostnameVerifier getHostnameVerifier() {
final Class<? extends HostnameVerifier> className = getClass(ConfigurationKeys.HOSTNAME_VERIFIER);
final String config = getString(ConfigurationKeys.HOSTNAME_VERIFIER_CONFIG);
if (className != null) {
if (config != null) {
return ReflectUtils.newInstance(className, config);
} else {
return ReflectUtils.newInstance(className);
}
}
return null;
}
@Override
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
setExceptionOnValidationFailure(getBoolean(ConfigurationKeys.EXCEPTION_ON_VALIDATION_FAILURE));
setRedirectAfterValidation(getBoolean(ConfigurationKeys.REDIRECT_AFTER_VALIDATION));
setUseSession(getBoolean(ConfigurationKeys.USE_SESSION));
if (!this.useSession && this.redirectAfterValidation) {
logger.warn("redirectAfterValidation parameter may not be true when useSession parameter is false. Resetting it to false in order to prevent infinite redirects.");
setRedirectAfterValidation(false);
}
setTicketValidator(getTicketValidator(filterConfig));
super.initInternal(filterConfig);
}
@Override
public void init() {
super.init();
CommonUtils.assertNotNull(this.ticketValidator, "ticketValidator cannot be null.");
}
/**
* Pre-process the request before the normal filter process starts. This could be useful for pre-empting code.
*
* @param servletRequest The servlet request.
* @param servletResponse The servlet response.
* @param filterChain the filter chain.
* @return true if processing should continue, false otherwise.
* @throws IOException if there is an I/O problem
* @throws ServletException if there is a servlet problem.
*/
protected boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
return true;
}
/**
* @Description: test
* @Param:
* @return:
* @Author: tst
* @Date: 2022-03-30
*/
protected String getServerName(HttpServletRequest request) {
return "";
}
/**
* Template method that gets executed if ticket validation succeeds. Override if you want additional behavior to occur
* if ticket validation succeeds. This method is called after all ValidationFilter processing required for a successful authentication
* occurs.
*
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
* @param assertion the successful Assertion from the server.
*/
protected void onSuccessfulValidation(final HttpServletRequest request, final HttpServletResponse response,
final Assertion assertion) {
// nothing to do here.
}
/**
* Template method that gets executed if validation fails. This method is called right after the exception is caught from the ticket validator
* but before any of the processing of the exception occurs.
*
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
*/
protected void onFailedValidation(final HttpServletRequest request, final HttpServletResponse response) {
// nothing to do here.
}
@Override
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
if (!preFilter(servletRequest, servletResponse, filterChain)) {
return;
}
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
//获取id地址
String serverName = getServerName(request);
final String ticket = retrieveTicketFromRequest(request);
if (CommonUtils.isNotBlank(ticket)) {
logger.debug("Attempting to validate ticket: {}", ticket);
try {
System.out.println("constructServiceUrl(request, response)one:" + constructServiceUrl(request, response));
System.out.println("constructServiceUrl(request, response,serverName)one:" + constructServiceUrl(request, response,serverName));
final Assertion assertion = this.ticketValidator.validate(ticket,
constructServiceUrl(request, response,serverName)); //这一句报错了
logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
request.setAttribute(CONST_CAS_ASSERTION, assertion);
if (this.useSession) {
request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
}
onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
logger.debug("Redirecting after successful ticket validation.");
//这里也要加上,条件成立后,默认去内网了
response.sendRedirect(constructServiceUrl(request, response,serverName));
return;
}
} catch (final TicketValidationException e) {
logger.debug(e.getMessage(), e);
onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) { //报错的
throw new ServletException(e);
}
response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
}
filterChain.doFilter(request, response);
}
public final void setTicketValidator(final TicketValidator ticketValidator) {
this.ticketValidator = ticketValidator;
}
public final void setRedirectAfterValidation(final boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
}
public final void setExceptionOnValidationFailure(final boolean exceptionOnValidationFailure) {
this.exceptionOnValidationFailure = exceptionOnValidationFailure;
}
public final void setUseSession(final boolean useSession) {
this.useSession = useSession;
}
}
2.4 AbstractCasFilter
AbstractCasFilter过滤器主要是重载 constructServiceUrl 这个方法
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.configuration.ConfigurationKeys;
import org.jasig.cas.client.util.AbstractConfigurationFilter;
import org.jasig.cas.client.util.CommonUtils;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
/**
* Abstract filter that contains code that is common to all CAS filters.
* <p>
* The following filter options can be configured (either at the context-level or filter-level).
* <ul>
* <li><code>serverName</code> - the name of the CAS client server, in the format: localhost:8080 or localhost:8443 or localhost or https://localhost:8443</li>
* <li><code>service</code> - the completely qualified service url, i.e. https://localhost/cas-client/app</li>
* </ul>
* <p>Please note that one of the two above parameters must be set.</p>
*
* @author Scott Battaglia
* @author Misagh Moayyed
* @since 3.1
*/
/**
* @Description: 这个过滤器非常重要,要重载constructServiceUrl这个方法
* @Param:
* @return:
* @Author: tst
* @Date: 2022-03-31
*/
public abstract class AbstractCasFilter extends AbstractConfigurationFilter {
/** Represents the constant for where the assertion will be located in memory. */
public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_";
private final Protocol protocol;
/** Sets where response.encodeUrl should be called on service urls when constructed. */
private boolean encodeServiceUrl = true;
/**
* The name of the server. Should be in the following format: {protocol}:{hostName}:{port}.
* Standard ports can be excluded. */
private String serverName;
/** The exact url of the service. */
private String service;
protected AbstractCasFilter(final Protocol protocol) {
this.protocol = protocol;
}
@Override
public final void init(final FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
if (!isIgnoreInitConfiguration()) {
setServerName(getString(ConfigurationKeys.SERVER_NAME));
setService(getString(ConfigurationKeys.SERVICE));
setEncodeServiceUrl(getBoolean(ConfigurationKeys.ENCODE_SERVICE_URL));
initInternal(filterConfig);
}
init();
}
/** Controls the ordering of filter initialization and checking by defining a method that runs before the init.
* @param filterConfig the original filter configuration.
* @throws ServletException if there is a problem.
*
*/
protected void initInternal(final FilterConfig filterConfig) throws ServletException {
// template method
}
/**
* Initialization method. Called by Filter's init method or by Spring. Similar in concept to the InitializingBean interface's
* afterPropertiesSet();
*/
public void init() {
CommonUtils.assertTrue(CommonUtils.isNotEmpty(this.serverName) || CommonUtils.isNotEmpty(this.service),
"serverName or service must be set.");
CommonUtils.assertTrue(CommonUtils.isBlank(this.serverName) || CommonUtils.isBlank(this.service),
"serverName and service cannot both be set. You MUST ONLY set one.");
}
// empty implementation as most filters won't need this.
@Override
public void destroy() {
// nothing to do
}
protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName,
this.protocol.getServiceParameterName(),
this.protocol.getArtifactParameterName(), this.encodeServiceUrl);
}
/**
* @Description: 重载的方法,serverName要传入
* @Param:
* @return:
* @Author: tst
* @Date: 2022-03-31
*/
protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response,String serverName) {
return CommonUtils.constructServiceUrl(request, response, this.service, serverName,
this.protocol.getServiceParameterName(),
this.protocol.getArtifactParameterName(), this.encodeServiceUrl);
}
/**
* Note that trailing slashes should not be used in the serverName. As a convenience for this common misconfiguration, we strip them from the provided
* value.
*
* @param serverName the serverName. If this method is called, this should not be null. This AND service should not be both configured.
*/
public final void setServerName(final String serverName) {
if (serverName != null && serverName.endsWith("/")) {
this.serverName = serverName.substring(0, serverName.length() - 1);
logger.info("Eliminated extra slash from serverName [{}]. It is now [{}]", serverName, this.serverName);
} else {
this.serverName = serverName;
}
}
public final void setService(final String service) {
this.service = service;
}
public final void setEncodeServiceUrl(final boolean encodeServiceUrl) {
this.encodeServiceUrl = encodeServiceUrl;
}
protected Protocol getProtocol() {
return this.protocol;
}
/**
* Template method to allow you to change how you retrieve the ticket.
*
* @param request the HTTP ServletRequest. CANNOT be NULL.
* @return the ticket if its found, null otherwise.
*/
protected String retrieveTicketFromRequest(final HttpServletRequest request) {
return CommonUtils.safeGetParameter(request, this.protocol.getArtifactParameterName(),
Arrays.asList(this.protocol.getArtifactParameterName()));
}
}
3. 在config类里面,使用新建的过滤器
在cas初始化创建相关对象的时候,registrationBean.setFilter()设置对应过滤器使用第二步我们自己新建的过滤器代替以前的。介绍如下:
1.登录认证使用 AuthenticationFilter
outCasServerLoginUrl: 对应配置文件中的cas-server-login-url-out参数
outServerName: 对应配置文件中的server-name-out参数
2.登录校验(ticket)使用Cas20ProxyReceivingTicketValidationFilter,
serverName: 对应配置文件中的server-name参数
outServerName: 对应配置文件中的server-name-out参数
4. 效验通过
在cas clent端的接口编写跳转的逻辑,设置token或者session,重定向不同的首页。
备注:
源码版本:cas-client-core-3.6.4-sources
下载地址:
https://search.maven.org/artifact/org.jasig.cas.client/cas-client-core/3.6.4/jar