概述
小记:
在做一套系统,准备接入集成登录。但是该系统对接的用户系统过多,所以要给每个用户系统在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单点登录相关内容所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复