我是靠谱客的博主 忧郁寒风,最近开发中收集的这篇文章主要介绍cas SSO单点登录相关内容,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

小记:

        在做一套系统,准备接入集成登录。但是该系统对接的用户系统过多,所以要给每个用户系统在web.xml中配置相应的过滤器,导致web.xml过于冗杂庞大,不利于管理。解决方案:给所有用户的登录请求配置成同一个登录请求,在web.xml里配置一套统一的过滤器,不配置init-param初始化参数,将初始化参数配置到用户各自的properties文件中,

例:casServerUrlPrefix=cas服务认证ip或域名

serverName=客户端ip或域名

encoding=UTF-8

casServerLoginUrl=cas服务认证ip或域名/login

然后阅读源码改写其获取init-param的方法,增加从properties读取文件的方式。以下是基于cas-client-core-3.2.1.jar包的获取初始化参数的源码

.

protected final String getPropertyFromInitParams(FilterConfig filterConfig,	String propertyName, String defaultValue) {
        // 此处是从properties获取初始化参数
		Properties prop = FileTools.readProperties("DBConfig_"	+ Formater.ntrim(FileTools.readProperties("SystemConfig.properties").getProperty("UnitCode")) + ".properties");
		String value0 = prop.getProperty(propertyName);
		if (CommonUtils.isNotBlank(value0)) {
			log.info((new StringBuilder()).append("Property [").append(propertyName).append("] loaded from Properties.getProperty with value [").append(value0).append("]").toString());
			return value0;
		}
        // 此处是从filterConfig获取初始化参数
		String value1 = filterConfig.getInitParameter(propertyName);
		if (CommonUtils.isNotBlank(value1)) {
			log.info((new StringBuilder()).append("Property [").append(propertyName).append("] loaded from FilterConfig.getInitParameter with value [").append(value1).append("]").toString());
			return value1;
		}
        // 此处是从ServletContext获取初始化参数
		String value2 = filterConfig.getServletContext().getInitParameter(
				propertyName);
		if (CommonUtils.isNotBlank(value2)) {
			log.info((new StringBuilder()).append("Property [").append(propertyName).append("] loaded from ServletContext.getInitParameter with value [").append(value2).append("]").toString());
			return value2;
		}
		InitialContext context;
		try {
			context = new InitialContext();
		} catch (NamingException e) {
			log.warn(e, e);
			return defaultValue;
		}
		String shortName = getClass().getName().substring(
				getClass().getName().lastIndexOf(".") + 1);
		String value3 = loadFromContext(
				context,
				(new StringBuilder()).append("java:comp/env/cas/")
						.append(shortName).append("/").append(propertyName)
						.toString());
		if (CommonUtils.isNotBlank(value3)) {
			log.info((new StringBuilder())
					.append("Property [")
					.append(propertyName)
					.append("] loaded from JNDI Filter Specific Property with value [")
					.append(value3).append("]").toString());
			return value3;
		}
		String value4 = loadFromContext(
				context,
				(new StringBuilder()).append("java:comp/env/cas/")
						.append(propertyName).toString());
		if (CommonUtils.isNotBlank(value4)) {
			log.info((new StringBuilder()).append("Property [")
					.append(propertyName)
					.append("] loaded from JNDI with value [").append(value4)
					.append("]").toString());
			return value4;
		} else {
			log.info((new StringBuilder()).append("Property [")
					.append(propertyName)
					.append("] not found.  Using default value [")
					.append(defaultValue).append("]").toString());
			return defaultValue;
		}
	}

第一次看源码的心得:难看懂的不是代码,在熟悉理解底层机制后,是很容易看懂源码的,因为并不需要每个方法都去理解,顺着流程走就行。例如明白filter的工作机制后,就直接去找其初始化方法init(FilterConfig filterConfig),里面调用了getPropertyFromInitParams获取初始化参数,然后就添加从properties读取参数的逻辑就可以了。

 

        因为需求原因,删掉了在web.xml里配置filter,直接将初始化操作和过滤操作写进了登录逻辑servlet的doPost方法里

以下是doPost方法的初始化工作和过滤操作。

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Cas20ProxyReceivingTicketValidationFilter ticketValidation = 
        new Cas20ProxyReceivingTicketValidationFilter();// 校验ticket的对象
    AuthenticationFilter authentication = new AuthenticationFilter();// 服务器认证对象
    HttpServletRequestWrapperFilter requestWrapper = new HttpServletRequestWrapperFilter();// 存储登录用户对象
    ticketValidation.init(1);	// 校验ticket对象的初始化
    authentication.init(1);		// 服务器认证对象的初始化
    requestWrapper.init(1);		// 存储登录用户对象的初始化
    if (ticketValidation.doFilter(request, response, 1))
    {
        if (authentication.doFilter(request, response, 1)){
            requestWrapper.doFilter(request, response, 1);
    }else{
        return;
        }
    }else{
        return;
    }
}

        改写了init方法的参数类型,因为用不到FilterConfig类了,也删掉了继承Filter类。doFilter方法也只是一个方法名,并不是过滤器的doFilter方法,这里懒得改名字就沿用了原方法名doFilter。以下是三个doFilter方法。修改了doFilter返回值为boolean,用来确定是否继续执行该servlet,模仿了过滤器的过滤功能,如果ticket校验结果为true则继续执行服务器认证,服务器认证结果为true则将登陆信息加载到request中。过滤器的链式过滤chan.doFilter的实现

ticket校验如下:ticketValidation.doFilter()

public class AuthenticationFilter extends AbstractCasFilter {
	
	private String casServerLoginUrl;	//sso中心认证服务的登录地址。
	private boolean renew = false;
	private boolean gateway = false;	//网关设置,为true能跳过登陆?
	private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();	//网关存储解析器。
	protected void initInternal(int filterConfig) throws ServletException {
		if (!isIgnoreInitConfiguration()) {
			super.initInternal(filterConfig);
			setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "casServerLoginUrl", null));
			this.log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
			setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
			this.log.trace("Loaded renew parameter: " + this.renew);
			setGateway(parseBoolean(getPropertyFromInitParams(filterConfig, "gateway", "false")));
			this.log.trace("Loaded gateway parameter: " + this.gateway);
			String gatewayStorageClass = getPropertyFromInitParams(
					filterConfig, "gatewayStorageClass", null);
			if (gatewayStorageClass != null) {
				try {
					this.gatewayStorage = ((GatewayResolver) Class.forName(gatewayStorageClass).newInstance());
				} catch (Exception e) {
					this.log.error(e, e);
					throw new ServletException(e);
				}
			}
		}
	}
	public void init() {
		super.init();
		CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
	}
	public final boolean doFilter(ServletRequest servletRequest, ServletResponse servletResponse, int filterChain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		//获取sso认证中心存储的session属性_const_cas_assertion_
		HttpSession session = request.getSession(false);
		// 该变量为判断用户是否已经登录的标记,在ticket认证成功后会被设置
		Assertion assertion = session != null ? (Assertion) session.getAttribute("_const_cas_assertion_") : null;
		// 如果登录过,则直接认证通过
		if (assertion != null) {
			return true;
		}
		// 从request中构建需要认证的服务url。如果该Url包含tikicet参数,则去除参数--http://218.242.158.194:18888/LoginByCQJTU去掉了后面的ticket=STXXX-XXX
		String serviceUrl = constructServiceUrl(request, response);
		// 从request中获取票据ticket。如果ticket存在,则获取URL后面的参数ticket
		String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());
		// 如果存在网关设置,则从session当中获取属性_const_cas_gateway的值为网关设置,并从session中去掉此属性。
		boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
		// 如果存在认证票据ticket或者网关设置,则直接认证通过。
		if ((CommonUtils.isNotBlank(ticket)) || (wasGatewayed)) {
			return true;
		}
		// 未登录,ticket不存在
		this.log.debug("no ticket and no assertion found");
		String modifiedServiceUrl;
		if (this.gateway) {
			this.log.debug("setting gateway attribute in session");
			//在session中设置网关属性session.setAttribute("_const_cas_gateway_", "yes")
			modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
		} else {
			modifiedServiceUrl = serviceUrl;
		}
		if (this.log.isDebugEnabled()) {
			this.log.debug("Constructed service url: " + modifiedServiceUrl);
		}
		// 如果用户没有登录过,那么构造重定向的URL
		String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
				getServiceParameterName(), modifiedServiceUrl, 
				this.renew, this.gateway);
		if (this.log.isDebugEnabled()) {
			this.log.debug("redirecting to "" + urlToRedirectTo + """);
		}
		// 重定向跳转到Cas认证中心,不走servlet路径,等待登录结果
		response.sendRedirect(urlToRedirectTo);
		return false;
	}
	public final void setRenew(boolean renew) {
		this.renew = renew;
	}
	public final void setGateway(boolean gateway) {
		this.gateway = gateway;
	}
	public final void setCasServerLoginUrl(String casServerLoginUrl) {
		this.casServerLoginUrl = casServerLoginUrl;
	}
	public final void setGatewayStorage(GatewayResolver gatewayStorage) {
		this.gatewayStorage = gatewayStorage;
	}
}

服务器端认证如下:authentication.doFilter()

/**
 * 服务器端认证
 * 第一次进入重定向到cas server,进入登录界面。用户信息认证通过后,创建了新的TGT后,缓存TGT,并且生成cookie,待后续把cookie写入客户端
 * 		然后验证是否存在Service,如果存在,生成ST,重定向用户到 Service 所在地址(附带该ST,并且会被过滤器拦截) , 并为客户端浏览器设置一个 Ticket Granted Cookie ( TGC )
 * 第二次得到ticket后会执行doFilter进行ticket验证,验证成功会设置assertion,并再次重定向到Service执行拦截器逻辑,assertion认证成功。
 * 
 * 登录后再次访问,则直接assertion认证成功,继续执行doFilter。
 */
public class AuthenticationFilter extends AbstractCasFilter {
	
	private String casServerLoginUrl;	//sso中心认证服务的登录地址。
	private boolean renew = false;
	private boolean gateway = false;	//网关设置?
	private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();	//网关存储解析器。
	protected void initInternal(int filterConfig) throws ServletException {
		if (!isIgnoreInitConfiguration()) {
			super.initInternal(filterConfig);
			setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "casServerLoginUrl", null));
			this.log.trace("Loaded CasServerLoginUrl parameter: " + this.casServerLoginUrl);
			setRenew(parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false")));
			this.log.trace("Loaded renew parameter: " + this.renew);
			setGateway(parseBoolean(getPropertyFromInitParams(filterConfig, "gateway", "false")));
			this.log.trace("Loaded gateway parameter: " + this.gateway);
			String gatewayStorageClass = getPropertyFromInitParams(
					filterConfig, "gatewayStorageClass", null);
			if (gatewayStorageClass != null) {
				try {
					this.gatewayStorage = ((GatewayResolver) Class.forName(gatewayStorageClass).newInstance());
				} catch (Exception e) {
					this.log.error(e, e);
					throw new ServletException(e);
				}
			}
		}
	}
	public void init() {
		super.init();
		CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
	}
	public final boolean doFilter(ServletRequest servletRequest, ServletResponse servletResponse, int filterChain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		//获取sso认证中心存储的session属性_const_cas_assertion_
		HttpSession session = request.getSession(false);
		// 该变量为判断用户是否已经登录的标记,在ticket认证成功后会被设置
		Assertion assertion = session != null ? (Assertion) session.getAttribute("_const_cas_assertion_") : null;
		// 如果登录过,则直接认证通过
		if (assertion != null) {
			return true;
		}
		// 从request中构建需要认证的服务url。如果该Url包含tikicet参数,则去除参数--http://218.242.158.194:18888/LoginByCQJTU去掉了后面的ticket=STXXX-XXX
		String serviceUrl = constructServiceUrl(request, response);
		// 从request中获取票据ticket。如果ticket存在,则获取URL后面的参数ticket
		String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());
		// 如果存在网关设置,则从session当中获取属性_const_cas_gateway的值为网关设置,并从session中去掉此属性。
		boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
		// 如果存在认证票据ticket或者网关设置,则直接认证通过。
		if ((CommonUtils.isNotBlank(ticket)) || (wasGatewayed)) {
			return true;
		}
		// 未登录,ticket不存在
		this.log.debug("no ticket and no assertion found");
		String modifiedServiceUrl;
		if (this.gateway) {
			this.log.debug("setting gateway attribute in session");
			//在session中设置网关属性session.setAttribute("_const_cas_gateway_", "yes")
			modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
		} else {
			modifiedServiceUrl = serviceUrl;
		}
		if (this.log.isDebugEnabled()) {
			this.log.debug("Constructed service url: " + modifiedServiceUrl);
		}
		// 如果用户没有登录过,那么构造重定向的URL
		String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
				getServiceParameterName(), modifiedServiceUrl, 
				this.renew, this.gateway);
		if (this.log.isDebugEnabled()) {
			this.log.debug("redirecting to "" + urlToRedirectTo + """);
		}
		// 重定向跳转到Cas认证中心,不走servlet路径,等待登录结果
		response.sendRedirect(urlToRedirectTo);
		return false;
	}
	public final void setRenew(boolean renew) {
		this.renew = renew;
	}
	public final void setGateway(boolean gateway) {
		this.gateway = gateway;
	}
	public final void setCasServerLoginUrl(String casServerLoginUrl) {
		this.casServerLoginUrl = casServerLoginUrl;
	}
	public final void setGatewayStorage(GatewayResolver gatewayStorage) {
		this.gatewayStorage = gatewayStorage;
	}
}

验证通过,设置用户信息。requestWrapper.doFilter(),其中修改了设置principal的方式,获取的时候不通过getUserPrincipal()和getRemoteUser(),直接通过request.getAttribute("principal")获得principal对象,然后principal.getName()就能获得用户唯一标时。

public final class HttpServletRequestWrapperFilter extends AbstractConfigurationFilter {
	private String roleAttribute;
	private boolean ignoreCase;
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, int filterChain) throws IOException, ServletException {
		AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);
		servletRequest.setAttribute("principal", principal);
		//ServletRequest servletrequest = new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal);
		//filterChain.doFilter(servletrequest, servletResponse);
	}
}

 

最后

以上就是忧郁寒风为你收集整理的cas SSO单点登录相关内容的全部内容,希望文章能够帮你解决cas SSO单点登录相关内容所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部