接着上一篇,在上一篇中我們描述了怎么在CAS SERVER登錄頁上添加驗證碼,並進行登錄。一旦CAS SERVER驗證成功后,我們就會跳轉到客戶端中去。跳轉到客戶端去后,大家想一想,客戶端總要獲取用戶信息吧,不然客戶端是怎么知道登錄的是哪個用戶。那么客戶端要怎么獲取用戶信息呢?
其實驗證成功,跳轉客戶端這個過程中,CAS SERVER 會返回登錄的相關信息給客戶端,客戶端只要進行獲取,就能知道登錄的具體是哪個用戶了。不過CAS 默認只返回用戶賬號給客戶端,那么怎么定義CAS SERVER返回的信息呢? 這就是本篇具體講解的內容了,大家聽我慢慢道來。
相關接口
在開始時,我們先了解下有關相關的幾個接口
- Credentials
- Principal
- IPersonAttributeDao
- PrincipalResolver
Credentials
Credentials (org.jasig.cas.authentication.Credentials)接口,我們在上一篇其實有使用過,我們當時有用過一個叫 UsernamePasswordCredential 的類,就是實現了Credentials接口。這個接口是用來定義我們登錄頁上輸入的認證信息的,比如用戶名、密碼、驗證碼等,可以理解為用戶認證的相關憑據。
Principal
Principal (org.jasig.cas.authentication.principal.Principal) 接口,這個主要是用來保存用戶認證后的用戶信息,信息保存在一個Map中。
IPersonAttributeDao
IPersonAttributeDao (org.jasig.services.persondir.IPersonAttributeDao) 接口,這個是用來定義我們需要返回給客戶端相關信息的接口,CAS SERVER 默認有提供許多實現,比如
- LdapPersonAttributeDao :通過查詢 LDAP 目錄 ,來返回信息
- SingleRowJdbcPersonAttributeDao : 通過JDBC SQL查詢,來返回信息
等等,還有許多,大家可以參考源碼中的實現,CAS SERVER 提供了各種功能的實現,有時候我們可以直接使用這個現成的就行了。
PrincipalResolver
PrincipalResolver(org.jasig.cas.authentication.principal.PrincipalResolver) 接口,上面有說到 Credentials 是從登錄頁面上進行獲取相關用戶信息的。那么認證成功后,怎么把Credentials里面的信息轉換到 Principal 中呢,這就是這個接口的作用了。由於認證本身是沒有返回用戶信息的,只是確定認證是通過還是沒有通過。這時還要用到我們上面的IPersonAttributeDao 接口,在這接口中我們就可以定義我們需要返回的信息了。
這接口中有兩個方法
- resolve : 解析Credentials中的信息,返回 Principal 接口
- supports : 判斷Credentials 是否支持 Principal 協議。
ps: 在3.x版本中沒有 PrincipalResolver接口,對應的是CredentialsToPrincipalResolver, PrincipalResolver這個是在4.0版本中加入的,大家要注意。
流程
相關接口講解后,大家應該對怎么返回信息有個大概的思路了。沒錯就是實現上面所說的 IPersonAttributeDao 、PrincipalResolver 接口 。下面根據代碼講解下具體的一個流程:
首先打開 deployerConfigContext.xml 文件,看下面的定義:
<!-- | Resolves a principal from a credential using an attribute repository that is configured to resolve | against a deployer-specific store (e.g. LDAP). --> <bean id="primaryPrincipalResolver" class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" > <property name="attributeRepository" ref="attributeRepository" /> </bean> <!-- Bean that defines the attributes that a service may return. This example uses the Stub/Mock version. A real implementation may go against a database or LDAP server. The id should remain "attributeRepository" though. +--> <bean id="attributeRepository" class="org.jasig.services.persondir.support.StubPersonAttributeDao" p:backingMap-ref="attrRepoBackingMap" /> <util:map id="attrRepoBackingMap"> <entry key="uid" value="uid" /> <entry key="eduPersonAffiliation" value="eduPersonAffiliation" /> <entry key="groupMembership" value="groupMembership" /> </util:map>
//PersonDirectoryPrincipalResolver 部分源碼
public final Principal resolve(final Credential credential) { logger.debug("Attempting to resolve a principal..."); String principalId = extractPrincipalId(credential); //extractPrincipalId 方法從credential中抽取id //省略... final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId); //根據IPersonAttributeDao 中的getPerson 獲取返回的屬性 final Map<String, List<Object>> attributes; //最終返回 Principal return new SimplePrincipal(principalId, convertedAttributes); }
具體流程:
1.從上面的deployerConfigContext.xml 配置我們可以看到,CAS 默認配置了一個叫做 PersonDirectoryPrincipalResolver 的類,在 這個類的 resolve 方法中有調用 extractPrincipalId 這個方法,這個方法傳入一個 Credentials 類型的參數,默認調用的是Credentials 的getId() 方法,CAS默認是返回用戶的userName,即登錄賬號。不過getId() 這個方法的實現我們可以在上一章中指定的UsernamePasswordCredential 類中自定義,一般是定義成返回用戶的userId或者其他唯一鍵,因為我們如果知道了用戶的userId,那么就可以根據這個從數據庫中查詢中用戶的一些具體信息了,進而就可以組成我們需要返回的信息。
2. 繼續往下看源碼,接着在 PersonDirectoryPrincipalResolver 中有注入一個 attributeRepository 屬性,這個就是上面的IPersonAttributeDao 接口,然后在resolve 方法中調用了 IPersonAttributeDao 接口 的getPerson方法,還傳入了一個參數principalId,其實這個傳入的參數就是我們上面 getId() 返回的值。
所以其實我們只要實現我們需要的 IPersonAttributeDao 就可以了。 下面給一個簡單的IPersonAttributeDao 例子:
public class BlogStubPersonAttributeDao extends StubPersonAttributeDao { @Override public IPersonAttributes getPerson(String uid) { Map<String, List<Object>> attributes = new HashMap<String, List<Object>>(); attributes.put("userid", Collections.singletonList((Object)uid)); attributes.put("cnblogUsername", Collections.singletonList((Object)"http://www.cnblogs.com/vhua")); attributes.put("cnblogPassword", Collections.singletonList((Object)"123456")); attributes.put("test", Collections.singletonList((Object)"test")); return new AttributeNamedPersonImpl(attributes); } }
這邊傳入的uid 默認是用戶的登錄名,我們這邊沒有做修改,直接用默認的。
這邊是只是測試用,所以就直接寫死了,實際開發肯定是需要在數據庫或者LDAP中進行查詢后,然后組裝成需要的信息 。
然后在 deployerConfigContext.xml 中修改
<bean id="primaryPrincipalResolver" class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" > <property name="attributeRepository" ref="attributeRepository" /> </bean>
<!-- 修改前 --> <bean id="attributeRepository" class="org.jasig.services.persondir.support.StubPersonAttributeDao" p:backingMap-ref="attrRepoBackingMap" /> <util:map id="attrRepoBackingMap"> <entry key="uid" value="uid" /> <entry key="eduPersonAffiliation" value="eduPersonAffiliation" /> <entry key="groupMembership" value="groupMembership" /> </util:map> <!-- 修改前 end-->
<!--修改后--> <bean id="attributeRepository" class="org.jasig.services.persondir.support.BlogStubPersonAttributeDao" /> <!--修改后 end-->
3. 修改完成后,我們還需要在 casServiceValidationSuccess.jspcas-server-webapp\src\main\webapp\WEB-INF\view\jsp\protocol\2.0\casServiceValidationSuccess.jsp)
添加一段代碼(下面紅色部分):
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:authenticationSuccess> <cas:user>${fn:escapeXml(assertion.primaryAuthentication.principal.id)}</cas:user>
<!-- 這段 -- > <c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}"> <cas:attributes> <c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"> <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}> </c:forEach> </cas:attributes> </c:if> <!-- 這段 end-- >
<c:if test="${not empty pgtIou}"> <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket> </c:if> <c:if test="${fn:length(assertion.chainedAuthentications) > 1}"> <cas:proxies> <c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1"> <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy> </c:forEach> </cas:proxies> </c:if> </cas:authenticationSuccess> </cas:serviceResponse>
4. 接下來 在客戶端設置信息的接收,我們直接在index.jsp中測試一下:
在java中可以通過下面的方式獲取
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
Map attributes = principal.getAttributes();
String xxx=attributes .get("xxx");
...
<!DOCTYPE html"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>返回值測試</title> </head> <body> <% request.setCharacterEncoding("UTF-8"); AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(); Map attributes = principal.getAttributes(); String userid=(String)attributes.get("userid"); String cnblogUsername = (String)attributes.get("cnblogUsername"); String cnblogPassword = (String)attributes.get("cnblogPassword"); String test=(String)attributes.get("test"); %> <div>飛奔的蝸牛博客:返回值演示</div> <ul> <li>userid:<%= userid%></li> <li>username:<%= cnblogUsername%></li> <li>password:<%= cnblogPassword%></li> <li>test:<%= test%></li> </ul> </body> </html>
效果
好了,我們登錄運行看看結果。
大家看到沒,已經獲取成功了,在CAS SERVER那邊設置的信息,我們正常獲取到了 。大家可以根據業務的需要,返回相關的信息。然后在客戶端進行操作。
總結
這一篇本來昨晚就寫好了,准備發布,結果因為太困,保存了草稿,上午才發現,大家湊合的看。
謝謝
打完收工。。。