cas支持内外网同时访问

2 篇文章 0 订阅

问题说明

最近项目需要解决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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值