自定義Token的CAS登入
工作中實際遇到的需求,我們有一箇舊系統,用了CAS的單點登入,現在有一個外部系統,準備從它那裡單點進來,這個外部系統提供了一個token引數來標記這是哪一個使用者,我們用他們提供的方式解析出對應的使用者,以這個使用者從CAS登入進系統。
有關CAS登入的分析網上多如牛毛,這裡不準備多作分析了,直接上解決過程。
這裡實現是基於我們以前系統的,是CAS 3.5.2
- 首先在登入流程檔案login-webflow.xml裡在on-start節點後面插入
<decision-state id="tokenCheck"> <if test="requestParameters.token != null and requestParameters.token != ''" then="tokenValidate" else="ticketGrantingTicketExistsCheck" /> </decision-state>
在這裡檢查是否有token引數,有的話執行token驗證,沒有的話走正常流程,ticketGrantingTicketExistsCheck就是原有的正常流程。
- 定義token驗證節點
<action-state id="tokenValidate"> <evaluate expression="tokenLoginAction.doExecute(flowRequestContext)" /> <transition on="error" to="generateLoginTicket" /> <transition on="success" to="sendTicketGrantingTicket" /> </action-state>
失敗則走generateLoginTicket分支,會生成一個LT,並重定向到登入頁面,這也是通常頁面登入失敗後的路徑
成功則走sendTicketGrantingTicket分支,即頁面正常登入成功時走的路徑
- 實現token驗證流程節點
在cas-servlet.xml裡新增新增tokenLoginAction Bean
<bean id="tokenLoginAction" class="org.jasig.cas.web.flow.TokenLoginAction" p:centralAuthenticationService-ref="centralAuthenticationService" />
實現TokenLoginAction
這裡主要解析token,並生成TGT。主要程式碼如下:
HttpServletRequest request = WebUtils.getHttpServletRequest(context); String token = request.getParameter("token"); try { //解析Token,略。。。。。。 CasCredentials credentials = new CasCredentials(); credentials.setUsername(userName); credentials.setPassword(""); credentials.setNoAuth(true); String tgt = centralAuthenticationService.createTicketGrantingTicket(credentials); WebUtils.putTicketGrantingTicketInRequestScope(context,tgt); } catch (Exception e) { e.printStackTrace(); return "error"; } return "success"; TokenLoginAction的主要邏輯程式碼
上面centralAuthenticationService是注入的屬性,
CasCredentials則是繼承自UsernamePasswordCredentials 的一個自定義Credentials,在使用者名稱、密碼基礎上添加了一個noAuth 屬性,用來標記是不是需要驗證密碼。這裡由外系統提供的token保證安全性,把noAuth設為true。
而登入驗證邏輯在createTicketGrantingTicket這個方法裡,驗證未通過會丟擲異常。
真正驗證的地方則是在authenticationManager裡,裡面有authenticationHandlers定義了驗證方法
- 修改登入驗證邏輯
login-webflow.xml頂部把credentials的定義先改了
<var name="credentials" class="com.cas.util.CasCredentials" />
deployerConfigContext.xml找到自定義登入驗證所在
<bean id="authenticationManager" class=""> <property name="authenticationHandlers"> <list> <bean class="com.cas.util.QueryUserAuthenticationHandler"> ...... </bean> </list> </property> </bean>
在這個QueryUserAuthenticationHandler class裡,驗證密碼之前加入
if (CasCredentials.class.isInstance(credentials)) { if (((CasCredentials)credentials).isNoAuth()) return true; }
這樣就完成了傳入第三方token的CAS登入。
似乎是完成了,但其實還有一些東西,
比如第三方進來的時候是不用他們傳Service 這個引數的,而這個引數是在CAS登入初始化時處理掉的,後面沒有地方自己往request里加這個引數讓CAS來處理它,自己寫requestscope裡寫Service物件又很麻煩,看了下程式碼,得注入很多東西才行。這樣就在進入CAS流程前,自己往請求裡塞一個Service 引數,然後重定向到CAS登入的url。
再比如,這次是別人提供Token用他們的方法解;以後有需求是我們提供一個Token出去,接收進來後用我們的方法自己解。所以其實TokenLoginAction那裡解析Token其實是解本方提供出去的Token。解別人的Token呢前置到塞Service 引數那個地方,那裡解析出來使用者名稱後,再用自己的方法生成一個Token發給CAS。這樣就把第三方的Token解析分離出去了,CAS登入的地方不會跟別人的實現綁在一起。