CAS 4.2.7版本,内网外网,自定义单点退出时调用的URL
前提: shiro+CAS
问题: 阿里云服务器,服务器有外网ip和内网ip, 两台服务器之间,用外网ip是访问不了的。
如果在shiro配置里,子系统地址都写的外网地址,那么验证ticket这步是过不去的,因为访问不了cas服务器嘛。
解决: (1)第一步,shiroFilter里有个loginUrl,这个直接写外网即可,没问题。 shiro发现没认证时,会返回302跳转,由浏览器跳转,所以应该是外网地址 如,http://www.aaa.com:28080/cas/login?service=http://www.bbb.com:38080/xxx/shiro-cas 在CAS服务器登录后,CAS服务器会用map记录ticket和service的关系,然后302跳转到www.bbb.com...,当然是带着ticket
(2)带着ticket过来后,shiro会调到CasRealm类,拿ticket到cas服务器去验证。 在配置文件里,你会配置一个bean,继承org.apache.shiro.cas.CasRealm类, 它有两个属性: CasServerUrlPrefix CasService 这两个就是验证ticket时,需要的参数
这个请求,是由你的子系统发起的,是两个服务器间的直接请求,所以, CasServerUrlPrefix要写为内网ip,如http://192.168.1.111:28080/cas
但是CasService还是要写第一步那个,如http://www.bbb.com:38080/xxx/shiro-cas 因为cas服务器会根据ticket从map里取得servcie,然后跟你传过去的service做匹配,不匹配就会报那个ticket无效的异常。
(3)单点退出 执行cas/logout后,cas服务器会通知你访问过的所有service,进行退出。 问题来了,这个请求是cas服务器直接请求你的子系统的,属于系统间的请求,应该用内网ip的嘛~~ 但是,上面搞了半天,也没告诉cas服务器,咱的子系统的内网地址的logoutURL啊~~
个人觉得,如果能在CasRealm那加个参数,如casServiceLogoutUrl,在验证ticket时,传给服务器,由服务器保存着, 单点退出时,拿这个url去调用,应该比较爽。
不过找了半天,貌似没这功能,自己搞也费劲。
搜了半天源代码,发现服务端的org.jasig.cas.logout.LogoutManagerImpl类的
public List<LogoutRequest> performLogout(final TicketGrantingTicket ticket) { final Map<String, Service> services = ticket.getServices(); final List<LogoutRequest> logoutRequests = new ArrayList<>(); // if SLO is not disabled if (!this.singleLogoutCallbacksDisabled) { // through all services for (final Map.Entry<String, Service> entry : services.entrySet()) { // it's a SingleLogoutService, else ignore final Service service = entry.getValue(); if (service instanceof SingleLogoutService) { final LogoutRequest logoutRequest = handleLogoutForSloService((SingleLogoutService) service, entry.getKey()); if (logoutRequest != null) { LOGGER.debug("Captured logout request [{}]", logoutRequest); logoutRequests.add(logoutRequest); } } } }
return logoutRequests; }
private LogoutRequest handleLogoutForSloService(final SingleLogoutService singleLogoutService, final String ticketId) { if (!singleLogoutService.isLoggedOutAlready()) {
final RegisteredService registeredService = servicesManager.findServiceBy(singleLogoutService); if (serviceSupportsSingleLogout(registeredService)) {
final URL logoutUrl = determineLogoutUrl(registeredService, singleLogoutService); final DefaultLogoutRequest logoutRequest = new DefaultLogoutRequest(ticketId, singleLogoutService, logoutUrl); final LogoutType type = registeredService.getLogoutType() == null ? LogoutType.BACK_CHANNEL : registeredService.getLogoutType();
switch (type) { case BACK_CHANNEL: if (performBackChannelLogout(logoutRequest)) { logoutRequest.setStatus(LogoutRequestStatus.SUCCESS); } else { logoutRequest.setStatus(LogoutRequestStatus.FAILURE); LOGGER.warn("Logout message not sent to [{}]; Continuing processing...", singleLogoutService.getId()); } break; default: logoutRequest.setStatus(LogoutRequestStatus.NOT_ATTEMPTED); break; } return logoutRequest; }
} return null; }
/** * Determine logout url. * * @param registeredService the registered service * @param singleLogoutService the single logout service * @return the uRL */ private URL determineLogoutUrl(final RegisteredService registeredService, final SingleLogoutService singleLogoutService) { try { URL logoutUrl = new URL(singleLogoutService.getOriginalUrl()); final URL serviceLogoutUrl = registeredService.getLogoutUrl();
if (serviceLogoutUrl != null) { LOGGER.debug("Logout request will be sent to [{}] for service [{}]", serviceLogoutUrl, singleLogoutService); logoutUrl = serviceLogoutUrl; } return logoutUrl; } catch (final Exception e) { throw new IllegalArgumentException(e); } }
大概意思是,根据TGT找到所有的service,然后循环调用 从最后一段代码可以看出,如果registeredService.getLogoutUrl()有值的话,会用它的值去logout。 没有的话,就用的原始的url,也就是咱传的service。
registeredService = servicesManager.findServiceBy(singleLogoutService);
找了半天,找到 org.jasig.cas.services.DefaultServicesManagerImpl
public void load() { final ConcurrentHashMap<Long, RegisteredService> localServices = new ConcurrentHashMap<>();
for (final RegisteredService r : this.serviceRegistryDao.load()) { LOGGER.debug("Adding registered service {}", r.getServiceId()); localServices.put(r.getId(), r); }
this.services = localServices; LOGGER.info("Loaded {} services from {}.", this.services.size(), this.serviceRegistryDao);
}
就相当于一个Map,从serviceRegistryDao获取的一个Map, serviceRegistryDao是在deployerConfigContext.xml中定义的, 默认是<alias name="jsonServiceRegistryDao" alias="serviceRegistryDao" />
cas.properties中有: ----------- ## # JSON Service Registry # # Directory location where JSON service files may be found. service.registry.config.location=classpath:services --------------
所以,看了半天,其实就是WEB-INF\classes\services下的那几个json文件。 理论上,你可以给每个子系统写一个json文件。
解决方案: 复制一个HTTPSandIMAPS-10000001.json,改为xxxx.json,名字随便起吧。
------ { "@class" : "org.jasig.cas.services.RegexRegisteredService", "serviceId" : "^http://www\\.aaa\\.com:38080/xxxxGate.*", ------>这行改成你的子系统地址,这是一个正则表达式 "name" : "agentTicketGate", -->名字,随便写 "id" : 10000003, -->id注意别相同啊 "description" : "agentTicketGate", -->描述,随便写 "proxyPolicy" : { "@class" : "org.jasig.cas.services.RefuseRegisteredServiceProxyPolicy" }, "evaluationOrder" : 2, -->匹配顺序吧,小的先匹配。HTTPSandIMAPS-10000001.json里写的是1000,也就是最后匹配 "logoutUrl" : "http://www.bbb.com:38080/xxxGate/shiro-cas", -->就是这个,哈哈 -----------------
特殊的工程都搞一个这样的文件,其他的都走HTTPSandIMAPS-10000001.json就行了。
效果就是,你用aaa.com访问子系统,但是单点退出时,CAS服务器,会调用bbb.com这个URL。
--------------------- 别人写的,单点退出的代码解析,可以看看 https://my.oschina.net/thinwonton/blog/1475562
|