http://www.ibloger.net/article/3129.html
一、什么是 Cas代理认证
考虑这样一种场景:有两个应用App1和App2,它们都是受Cas Server保护的,即请求它们时都需要通过Cas Server的认证。现需要在App1中通过Http请求访问App2,显然该请求将会被App2配置的Cas的AuthenticationFilter拦截并转向Cas Server,Cas Server将引导用户进行登录认证,这样我们也就不能真正的访问到App2了。针对这种应用场景,Cas也提供了对应的支持。
二、Cas 代理认证原理
Cas Proxy可以让我们轻松的通过App1访问App2时通过Cas Server的认证,从而访问到App2。其主要原理是这样的,App1先通过Cas Server的认证,然后向Cas Server申请一个针对于App2的proxy ticket,之后在访问App2时把申请到的针对于App2的proxy ticket以参数ticket传递过去。App2的AuthenticationFilter将拦截到该请求,发现该请求携带了ticket参数后将放行交由后续的Ticket Validation Filter处理。Ticket Validation Filter将会传递该ticket到Cas Server进行认证,显然该ticket是由Cas Server针对于App2发行的,App2在申请校验时是可以校验通过的,这样我们就可以正常的访问到App2了。
针对Cas Proxy的原理,官网有一张图很能说明问题(官网看着不爽,我重新写了一个),如下所示。
三、Cas 代理如何配置
Cas Proxy实现的核心是Cas20ProxyReceivingTicketValidationFilter,该Filter是Ticket Validation Filter的一种。使用Cas Proxy时我们需要使用Cas20ProxyReceivingTicketValidationFilter作为我们的Ticket Validation Filter,而且对于代理端而言该Filter需要放置在AuthenticationFilter之前。对于上述应用场景而言,App1就是我们的代理端,而App2就是我们的被代理端。
Cas20ProxyReceivingTicketValidationFilter在代理端与被代理端的配置是不一样的。
在我的Demo中,用的是Cas30ProxyReceivingTicketValidationFilter验证器,但其实如果你观察源码就会发现Cas30ProxyReceivingTicketValidationFilter实际上是继承自Cas20ProxyReceivingTicketValidationFilter,通过下面的图可以看出继承关系
我为什么会使用Cas30ProxyReceivingTicketValidationFilter?
这是因为我喜欢最新的内容,CAS是基于HTTP2,3的协议,要求每个组件都可以通过特定的URI访问。
如下表格, Cas30
用的就是/p3/xxx
,这点通过debug或查看cas-client.jar
源码就能发现.
URI | 描述 |
---|---|
/login | 登录 |
/logout | 销毁CAS会话(注销) |
/validate | service ticket validation |
/serviceValidate | service ticket validation [CAS 2.0] |
/proxyValidate | service/proxy ticket validation [CAS 2.0] |
/proxy | proxy ticket service [CAS 2.0] |
/p3/serviceValidate | service ticket validation [CAS 3.0] |
/p3/proxyValidate | service/proxy ticket validation [CAS 3.0] |
Cas20ProxyReceivingTicketValidationFilter 配置参数介绍
属性 描述 需要
casServerUrlPrefix CAS服务器URL的开始,即https://localhost:8443/cas 是
serverName 此应用程序所在的服务器的名称。服务URL将使用此动态构建,即https://localhost:8443(您必须包含协议,但如果端口是标准端口,则端口是可选的) 是
renew 指定是否renew=true应该发送到CAS服务器。有效值是true/false(或根本没有值)。请注意,renew不能将其指定为本地init-param设置 否
redirectAfterValidation 是否在故障单验证后重定向到相同的URL,但没有参数中的故障单。默认为true 否
useSession 是否在会话中存储断言。如果不使用会话,则每个请求都需要票证。默认为true 否
exceptionOnValidationFailure 是否在票证验证失败时抛出异常。默认为true 否
proxyReceptorUrl 要查看PGTIOU/PGT来自CAS服务器的响应的URL 。应该从上下文的根来定义。例如,如果您的应用程序部署在/cas-client-app您想要的代理服务器URL中,/cas-client-app/my/receptor您需要配置proxyReceptorUrl/my/receptor` 否
acceptAnyProxy 指定是否有任何代理正常。默认为false。 没有
allowedProxyChains 指定代理链。每个可接受的代理链应包含一个由空格分隔的URL列表(用于完全匹配)或URL的正则表达式(由^字符开始)。每个可接受的代理链应该出现在自己的行上 否
proxyCallbackUrl 用于提供CAS服务器以接受代理授予票证的回叫URL 否
proxyGrantingTicketStorageClass 指定具有无参数构造函数的ProxyGrantingTicketStorage类的实现。 否
sslConfigFile 包含用于客户端SSL配置的SSL设置的属性文件的引用,用于反向通道调用期间。该配置包括用于键protocol默认为SSL,keyStoreType,keyStorePath,keyStorePass,keyManagerType默认为SunX509和certificatePassword。 否
encoding 指定客户端应使用的编码字符集 否
secretKey proxyGrantingTicketStorageClass它使用的密钥,如果它支持加密。 否
cipherAlgorithm 该算法使用的proxyGrantingTicketStorageClass是否支持加密。默认为DESede 否
millisBetweenCleanUps 清理任务的启动延迟从存储中删除过期票证。默认为60000 msec 否
ticketValidatorClass 要使用/创建票证验证程序类 没有
hostnameVerifier 主机名验证程序类名称,用于进行反向通话 否
四、项目实战
项目采用 SpringBoot + maven 方式构建
文件名称 域名 功能
client1 http://client1.com:8888/ 客户端1 后台接口(代理方)
client2 http://client2.com:8889/ 客户端2 后台接口(被代理方)
cas-server-rest http://cas.server.com:8484/cas CAS-Server
1、本地hosts文件配置如下
127.0.0.1 cas.server.com
127.0.0.1 client1.com
127.0.0.1 client2.com
2、代理方配置
既然Cas20ProxyReceivingTicketValidationFilter是一个Ticket Validation Filter,所以之前我们介绍的Ticket Validation Filter需要配置的参数,在这里也需要配置,所不同的是对于代理端的Cas20ProxyReceivingTicketValidationFilter必须指定另外的两个参数:roxyCallbackUrl和proxyReceptorUrl。
proxyCallbackUrl:用于指定一个回调地址,在代理端通过Cas Server校验ticket成功后,Cas Server将回调该地址以传递pgtId和pgtIou,Cas20ProxyReceivingTicketValidationFilter在接收到对应的响应后会将它们保存在内部持有的ProxyGrantingTicketStorage中。之后在对传递过来的ticket进行validate的时候又会根据pgtIou从ProxyGrantingTicketStorage中获取对应的pgtId,用以保存在AttributePrincipal中,而AttributePrincipal又会保存在Assertion中。proxyCallbackUrl因为是指定Cas Server回调的地址,所以其必须是一个可以供外部访问的绝对地址。此外,因为Cas Server默认只回调使用安全通道协议https进行通信的地址,所以我们的proxyCallbackUrl需要是一个使用https协议访问的地址。
proxyReceptorUrl:该地址是proxyCallbackUrl相对于代理端的一个地址, Cas20ProxyReceivingTicketValidationFilter将根据该地址来决定请求是否来自Cas Server的回调。
下面是一个Cas30ProxyReceivingTicketValidationFilter在代理端配置的示例
Java
/**
Cas30ProxyReceivingTicketValidationFilter 验证过滤器
该过滤器负责对Ticket的校验工作,必须启用它
*@return
/
@Bean
public FilterRegistrationBean filterValidationRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
// 设定匹配的路径
registration.addUrlPatterns(“/“);
Map<String, String> initParameters = new HashMap();
initParameters.put(“casServerUrlPrefix”, CasConfig.CAS_SERVER_PATH);
initParameters.put(“serverName”, CasConfig.SERVER_NAME);initParameters.put(“proxyCallbackUrl”, CasConfig.PROXY_CALLBACK_URL);
initParameters.put(“proxyReceptorUrl”, “/proxy/callback”);// 是否对serviceUrl进行编码,默认true:设置false可以在302对URL跳转时取消显示;jsessionid=xxx的字符串
// 观察CommonUtils.constructServiceUrl方法可以看到
initParameters.put(“encodeServiceUrl”, “false”);registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
代理请求示例
Java
package com.tingfeng.controller;
import com.tingfeng.utils.HttpProxy;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AssertionHolder;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.security.Principal;
import java.util.Arrays;
/**
Cas代理回调处理
*/
@RestController
@RequestMapping(“/proxy”)
public class CasProxyController {@GetMapping(“/users”)
public String proxyUsers(HttpServletRequest request, HttpServletResponse response) {String result = "无结果"; try { String serviceUrl = "http://client2.com:8889/user/users"; AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(); //1、获取到AttributePrincipal对象
// AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal();
if (principal == null) { return "用户未登录"; } //2、获取对应的(PT)proxy ticket String proxyTicket = principal.getProxyTicketFor(serviceUrl); if (proxyTicket == null) { return "PGT 或 PT 不存在"; } //3、请求被代理应用时将获取到的proxy ticket以参数ticket进行传递 String url = serviceUrl + "?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8"); result = HttpProxy.httpRequest(url, "", HttpMethod.GET); System.out.println("result结果:" + result); } catch (Exception e) { e.printStackTrace(); } return result;
}
@GetMapping("/books")
public String proxyBooks(HttpServletRequest request, HttpServletResponse response) {
String result = "无结果";
try {
String serviceUrl = "http://client2.com:8889/book/books";
//1、获取到AttributePrincipal对象
AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal();
if (principal == null) {
return "用户未登录";
}
//2、获取对应的(PT)proxy ticket
String proxyTicket = principal.getProxyTicketFor(serviceUrl);
if (proxyTicket == null) {
return "PGT 或 PT 不存在";
}
//3、请求被代理应用时将获取到的proxy ticket以参数ticket进行传递
String url = serviceUrl + "?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8");
result = HttpProxy.httpRequest(url, "", HttpMethod.GET);
System.out.println("result结果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
3、被代理端配置
在被代理端Cas30ProxyReceivingTicketValidationFilter是扮演Ticket Validation Filter的角色,它可以验证正常通过Cas Server登录认证成功后返回的ticket,也可以认证来自其它代理端传递过来的proxy ticket(PT),当然,最终的认证都是通过Cas Server来完成的。既然Cas30ProxyReceivingTicketValidationFilter在被代理端是作为Ticket Validation Filter来使用的,可以有的参数其都可以配置。在被代理端需要配置一个参数用以表示接受来自哪些应用的代理,这个参数可以是acceptAnyProxy,也可以是allowedProxyChains。
acceptAnyProxy:表示接受所有的,其对应的参数值是true或者false
allowedProxyChains:指定具体接受哪些应用的代理,多个应用就写多行,allowedProxyChains的值对应的是代理端提供给Cas Server的回调地址,如果使用前文示例的代理端配置,我们就可以指定被代理端的allowedProxyChains为https://elim:8043/app1/proxyCallback,这样当app1作为代理端来访问该被代理端时就能通过验证,得到正确的响应。
下面是一个被代理端配置Cas30ProxyReceivingTicketValidationFilter的配置示例。 多了一个acceptAnyProxy参数而已。
Java
@Bean
public FilterRegistrationBean filterValidationRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
// 设定匹配的路径
registration.addUrlPatterns(“/*”);
Map<String, String> initParameters = new HashMap();
initParameters.put(“casServerUrlPrefix”, CasConfig.CAS_SERVER_PATH);
initParameters.put(“serverName”, CasConfig.SERVER_NAME);
initParameters.put(“encodeServiceUrl”, “false”);
initParameters.put("acceptAnyProxy", "true"); // 接收任何代理
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
4、cas-server端配置
proxyCallbackUrl回调地址官方默认必须要配置一个https的协议,这也就意味着我们的线上项目,必须支持https才可以。而我们的线上项目,大多只支持http协议,这该怎么办呢?
经过不懈的google和cas官网文档查询,并没有发现有相关文章介绍如何支持http协议。最后经过debug查询cas-server源码发现,最终cas是根据一个有关代理认证策略的正则有关系,默认不允许颁发PGT,于是就修改了json格式service文件服务中的策略。问题得以解决。(非常郁闷,为啥CAS默认都https了,怎么就不能好好地默认代理策略开放呢!)
cas代理认证取消HTTPS,支持HTTP方式
默认的json服务策略中,不支持代理返回PGT,需要在自定义的service中增加proxyPolicy策略。在pattern中,设置协议的正则规则即可。
Markdown
{
“@class” : “org.apereo.cas.services.RegexRegisteredService”,
“serviceId” : “^(https|http|imaps)://.*”,
“name” : “HTTPS and HTTP and IMAPS”,
“id” : 10000001,
“description” : “This service definition authorizes all application urls that support HTTPS and HTTP and IMAPS protocols.”,
“evaluationOrder” : 10000,
“attributeReleasePolicy”: {
“@class”: “org.apereo.cas.services.ReturnAllAttributeReleasePolicy”
},
“proxyPolicy”: {
“@class”: “org.apereo.cas.services.RegexMatchingRegisteredServiceProxyPolicy”,
“pattern”: “^(https|http)?://.*”
}
}
002.png
4.1 cas代理使用HTTPS,支持HTTP方式
如果你认真的看源代码,会观察到cas-server的HttpBasedServiceCredentialsAuthenticationHandler类中authenticate方法,如下
Java
public HandlerResult authenticate(Credential credential) throws GeneralSecurityException {
HttpBasedServiceCredential httpCredential = (HttpBasedServiceCredential)credential;
if (!httpCredential.getService().getProxyPolicy().isAllowedProxyCallbackUrl(httpCredential.getCallbackUrl())) {
LOGGER.warn(“Proxy policy for service [{}] cannot authorize the requested callback url [{}].”, httpCredential.getService().getServiceId(), httpCredential.getCallbackUrl());
throw new FailedLoginException(httpCredential.getCallbackUrl() + “ cannot be authorized”);
} else {
LOGGER.debug(“Attempting to authenticate [{}]”, httpCredential);
URL callbackUrl = httpCredential.getCallbackUrl();
if (!this.httpClient.isValidEndPoint(callbackUrl)) {
throw new FailedLoginException(callbackUrl.toExternalForm() + “ sent an unacceptable response status code”);
} else {
return new DefaultHandlerResult(this, httpCredential, this.principalFactory.createPrincipal(httpCredential.getId()));
}
}
}
getProxyPolicy()方法获取service的策略并根据isAllowedProxyCallbackUrl方法判断正则表达式是否允许使用https或http,那么直接设置正则就好了。将上面的”pattern”: “^http?://.*” 修改为”pattern”: “^(https|http)?://.*”即可,我已经测试过没问题。
使用https,你需要配置client支持https才行。配置很简单,查看我的github源码即可。 如果你不会配置,可能会遇到cas-server向客户端发送代理回调时出现PKIX path building failed等问题。查看下面的常见问题解决即可。
4.2 补充1:
在pom.xml文件中,加入如下配置,虽然对正确结果没有影响,只是方便Debug查看出现的验证方面问题。
4.3 补充2:
另外看到官网给出的参数有关于http代理的,但是具体和代理认证有没有关系没得到答案,难道是取消cas服务端的https协议?测试过程中没有明星感知,就删除了,后面的就没再application.properties中配置,如有知道代表什么含义的,还请给我留言,感激不尽。
http-proxying:https://apereo.github.io/cas/5.2.x/installation/Configuration-Properties.html#http-proxying
最后编辑:Jeebiz 更新时间:2024-05-06 16:13