1.1 原理
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的原理,官網有一張圖很能說明問題,如下所示。
1.2 配置
Cas Proxy實現的核心是Cas20ProxyReceivingTicketValidationFilter,該Filter是Ticket Validation Filter的一種。使用Cas Proxy時我們需要使用Cas20ProxyReceivingTicketValidationFilter作爲我們的Ticket Validation Filter,而且對於代理端而言該Filter需要放置在AuthenticationFilter之前。對於上述應用場景而言,App1就是我們的代理端,而App2就是我們的被代理端。Cas20ProxyReceivingTicketValidationFilter在代理端與被代理端的配置是不一樣的。我們先來看一下在代理端的配置。
1.2.1代理端
既然Cas20ProxyReceivingTicketValidationFilter是一個Ticket Validation Filter,所以之前我們介紹的Ticket Validation Filter需要配置的參數,在這裏也需要配置,Ticket Validation Filter可以配置的參數這裏也可以配置。所不同的是對於代理端的Cas20ProxyReceivingTicketValidationFilter必須指定另外的兩個參數,proxyCallbackUrl和proxyReceptorUrl。
l 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協議訪問的地址。
l proxyReceptorUrl:該地址是proxyCallbackUrl相對於代理端的一個地址,Cas20ProxyReceivingTicketValidationFilter將根據該地址來決定請求是否來自Cas Server的回調。
下面是一個Cas20ProxyReceivingTicketValidationFilter在代理端配置的示例,需要注意的是該Filter需要配置在AuthenticationFilter之前,所以完整配置如下:
<context-param>
<param-name>serverName</param-name>
<param-value>https://elim:8043</param-value>
</context-param>
<filter>
<filter-name>proxyValidationFilter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://elim:8443/cas</param-value>
</init-param>
<init-param>
<param-name>proxyCallbackUrl</param-name>
<param-value>https://elim:8043/app1/proxyCallback</param-value>
</init-param>
<init-param>
<param-name>proxyReceptorUrl</param-name>
<param-value>/proxyCallback</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>proxyValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casAuthenticationFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://elim:8443/cas/login</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>casAuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casHttpServletRequestWrapperFilter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>casHttpServletRequestWrapperFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casAssertionThreadLocalFilter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>casAssertionThreadLocalFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.2.2被代理端
在被代理端Cas20ProxyReceivingTicketValidationFilter是扮演Ticket Validation Filter的角色,它可以驗證正常通過Cas Server登錄認證成功後返回的ticket,也可以認證來自其它代理端傳遞過來的proxy ticket,當然,最終的認證都是通過Cas Server來完成的。既然Cas20ProxyReceivingTicketValidationFilter在被代理端是作爲Ticket Validation Filter來使用的,所以Ticket Validation Filter可以有的參數其都可以配置。在被代理端需要配置一個參數用以表示接受來自哪些應用的代理,這個參數可以是acceptAnyProxy,也可以是allowedProxyChains。acceptAnyProxy表示接受所有的,其對應的參數值是true或者false;而allowedProxyChains則用以指定具體接受哪些應用的代理,多個應用就寫多行,allowedProxyChains的值對應的是代理端提供給Cas Server的回調地址,如果使用前文示例的代理端配置,我們就可以指定被代理端的allowedProxyChains爲“https://elim:8043/app1/proxyCallback”,這樣當app1作爲代理端來訪問該被代理端時就能通過驗證,得到正確的響應。下面是一個被代理端配置Cas20ProxyReceivingTicketValidationFilter的完整配置示例。
<context-param>
<param-name>serverName</param-name>
<param-value>http://elim:8081</param-value>
</context-param>
<filter>
<filter-name>casSingleSignOutFilter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>casSingleSignOutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casAuthenticationFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https:// elim:8443/cas/login</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>casAuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>proxyValidationFilter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://elim:8443/cas</param-value>
</init-param>
<init-param>
<param-name>acceptAnyProxy</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>proxyValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casHttpServletRequestWrapperFilter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>casHttpServletRequestWrapperFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>casAssertionThreadLocalFilter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>casAssertionThreadLocalFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.3 請求示例
配置好以後接下來將展示一個app1作爲代理端訪問app2的應用示例。該示例的重點在於app1的請求發起,對於需要請求的app2端的內容我們假設就是一個簡單的jsp文件,其簡單的輸出一些文本。對於代理端而言,其請求的發起通常需要經過如下步驟:
1、獲取到當前的AttributePrincipal對象,如果當前可以獲取到request對象並且使用了HttpServletRequestWrapperFilter,我們則可以直接從request中獲取。
AttributePrincipal principal = (AttributePrincipal) req.getUserPrincipal();
當然,如果使用了AssertionThreadLocalFilter,我們也可以從AssertionHolder中獲取Assertion,進而獲取到對應的AttributePrincipal對象。
AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal();
2、通過AttributePrincipal獲取針對於被代理端對應的proxy ticket,該操作將促使AttributePrincipal向Cas Server發起請求,從而獲取到對應的proxy ticket。針對同一URL每次從Cas Server請求獲取到的proxy ticket都是不一樣的。以下是一個獲取針對於“http://elim:8081/app2/getData.jsp”的proxy ticket的示例:
String proxyTicket = principal.getProxyTicketFor("http://elim:8081/app2/getData.jsp");
3、在請求被代理端時將獲取到的proxy ticket以參數ticket一起傳遞過去,如:
URL url = new URL("http://elim:8081/app2/getData.jsp?ticket=" + proxyTicket);
完整的示例代碼如下所示:
@WebServlet(name="casProxyTest", urlPatterns="/cas/proxy/test")
public class CasProxyTestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1、獲取到AttributePrincipal對象
AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal();
//2、獲取對應的proxy ticket
String proxyTicket = principal.getProxyTicketFor("http://elim:8081/app/getData.jsp");
//3、請求被代理應用時將獲取到的proxy ticket以參數ticket進行傳遞
URL url = new URL("http://elim:8081/app/getData.jsp?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8"));
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuffer content = new StringBuffer();
String line = null;
while ((line=br.readLine()) != null) {
content.append(line).append("<br/>");
}
resp.getWriter().write(content.toString());
}
}
(注:本文是基於Cas Server3.5.2和Cas Client3.1.11所寫,原文地址:http://haohaoxuexi.iteye.com/blog/2145751)