---
# System prepended metadata

title: cas-sample-java-webapp tutorial
tags: [CAS]

---

<style>
.red {
    color: red;
}
</style>
# cas-sample-java-webapp tutorial
###### tags: `CAS`

## 前置作業:
* 建立cas client專案以前，需要搭配cas server建立，目前cas server是透過tomcat配置，詳細配置可參考: [架設CAS Server and Client Tomcat SSL 設定](/rqPAuRsQRxm87BnavdxVeQ)

* 本篇會使用到的範例憑證檔案，含: casclient1.keystore、casclient1.crt

* 本專案配置在glassfish server上，一樣需要將上述兩個檔案匯入到glassfish server內部，詳細配置可參考: [Glassfish ssl及自建domain設定](/nx5oFMx0Rq66YJWM1XM0Mw)


* 本專案jar檔管理有套用到maven，所有使用到的jar在`cas-sample-java-webapp1\src\main\java\libs`裡面皆有備份，可透過本地方式配置dependencies

* 本專案僅只作為範例，實作時需自行依據所需去撰寫相關了jsf的xhtml以及相關的bean.java等等，以及需要自行設定filter的流向。


## 主要介紹:
#### 1. 先設定web.xml，包含cas login、filter控制request流向等等。

* 設定single sign out必要的listener，僅需依照預設保留即可。
```xml=
<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
```

* single sign out filter: 設定cas server的位子，可替換成server實際ip位子，配置的方式透過forward才會進入，後續會解釋為什麼。

  此filter作用是為導到cas server去檢查是否登入狀態，基本上會設定在login的位子

```xml=
<filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>https://sso.server.com:8443/cas</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/login.xhtml</url-pattern>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>
```

* cas authentication filter: casServerLoginUrl代表cas login的位子，因為cas是在server端做登入管理，因此必須設定cas server位子。 serverName代表client初始位子。

  此filter是用來導入到cas server login page的地方，做登入驗證的部分
```xml=
<filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://sso.server.com:8443/cas/login</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>https://sso.client1.com:18443/cas-sample-java-webapp1/index.xhtml</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/login.xhtml</url-pattern>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>
```

* CAS HttpServletRequest Wrapper Filter: 非必要配置，單純可透過HttpServletRequest的getRemoteUser()方法獲得SSO登入使用者的登入名。

* loginFilter: 所有request都必須流經此filter，先行判斷是否包含jwt token、是否已登入等等

* validatorFilter: 必定要有的filter，負責檢查所有request的jwt token是否合格、簽發時間是否過時等等

* Faces Servlet: jsf套件需要的相關配置。

#### 2. 撰寫loginFilter.java
```java=
if(httpRequest.getServletPath().contains("css") || httpRequest.getQueryString() != null) {
    chain.doFilter(request, response);
} else if(httpRequest.getServletPath().equals("/logout.xhtml")) {
    chain.doFilter(request, response);
} else if(httpRequest.getServletPath().equals("/login.xhtml")) {
    httpRequest.getRequestDispatcher("/login.xhtml").forward(request, response);
} else if(httpRequest.getSession().getAttribute("jwt") == null) {
    httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.xhtml");
} else {
    chain.doFilter(request, response);
}
```
負責管控所有request的第一層控制，範例中會有`httpRequest.getServletPath().contains("css")`是因為當經過cas server login完畢之後，流經faces servlet之後，還會再發一個關於jsf css的request過來，請直接pass過去即可。

其餘就是一些jwt檢查的判斷，若已經在登入狀態就直接流到最後即可。


#### 3. 撰寫validateFilter.java
```java=
if (httpRequest.getServletPath().contains("css")) {
    chain.doFilter(request, response);
} else if (jwtValidate.jwtvalid(httpRequest) && userlist.contains(httpRequest.getSession().getId())) {
    if (httpRequest.getServletPath().equals("/login.xhtml")) {
        httpResponse.sendRedirect(httpRequest.getContextPath() + "/index.xhtml");
    } else {
        chain.doFilter(request, response);
    }
} else {
    httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.xhtml");
}

```
此filter會實作jwt檢查的部分，主要由`jwtValidate.jwtvalid(request)`來執行，JwtValidate使用相關可參考[SSO 技術實踐: JwtValidate.jar使用相關說明](/Z0FxKELtR3io9loq5pUqzQ)，有個重點是這邊的Request可接收jwt的狀況分兩種: 一種是在session裡面塞"jwt"的屬性，或者是cas server回傳網址會含有ticket=xxx(這就是jwt)才有辦法驗證。

#### 4. 撰寫logoutFilter.java
```java=
String body = request.getReader().lines().collect(Collectors.joining());
System.out.println("body: " + URLDecoder.decode(body, "UTF-8"));

String samlString = "";
if (body.contains("logoutRequest=")) {
    samlString = URLDecoder.decode(body, "UTF-8").split("logoutRequest=")[1];
}

if (userlist.contains(httpRequest.getSession().getId())) {
    // local logout
    HttpSession session = userlist.getSessionById(httpRequest.getSession().getId());
    userlist.removeUserById(httpRequest.getSession().getId());
    session.invalidate();
} else if (samlString != null) {
    // cas server single sign out
    HashMap<String, String> attributes = samlUtils.getAttributes(samlString);
    HttpSession session = userlist.getSessionByST(attributes.get("SessionIndex"));

    if (session != null) {
        userlist.removeUserByST(attributes.get("SessionIndex"));
        session.invalidate();
    }
}

httpResponse.sendRedirect("https://sso.server.com:8443/cas/logout?service=https://sso.client1.com:18443/cas-sample-java-webapp1/login.xhtml");
```
負責登出的部分，主要分為兩種，一種是cas client端local登出，本身session直接作廢；另一種則是當有其他的client登出後，透過cas server發送logout request過來通知自己本身也要登出時的狀況。

由於透過logout request的部分，會需要解析saml的部分，這部分也已經打包在JwtValidate.jar裡面了，可直接import SamlUtils來使用，發過來字串中會有`SessionIndex`的屬性，可取得是哪個session id需要作廢。

JwtValidate.jar本身有包含一些簡單的bean用來管理user和user清單，這部分可考慮是否自行撰寫或擴增，並覆蓋到jar檔裡面

#### 5. 剩下就是撰寫相關的xhtml以及bean了，在此不多贅述。