CAS SSO 4.0 單點登入返回更多使用者資訊
從cas server登入成功後,預設只能從cas server得到使用者名稱。但程式中也可能遇到需要得到更多如姓名,手機號,email等更多使用者資訊的情況。
cas client拿到使用者名稱後再到資料庫中查詢,的確可以得到關於該使用者的更多資訊。
但是如果使用者登入成功後,直接從cas server返回給cas client使用者的詳細資訊,這也是一個不錯的做法。這個好處,尤其是在分散式中得以彰顯,cas server可以把使用者資訊傳遞給各個應用系統,如果是上面那種做法,那麼各個系統得到使用者名稱後,都得去資料庫中查詢一遍,無疑是一件重複性工作。
一、首先需要配置屬性attributeRepository:
WEB-INF目錄找到 deployerConfigContext.xml檔案,同時配置 attributeRepository 如下:
<bean class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" id="attributeRepository"> <constructor-arg index="0" ref="dataSource"/> <constructor-arg index="1" value="select * from t_user where {0}"/> <property name="queryAttributeMapping"> <map> <!--這裡的key需寫username和登入頁面一致,value對應資料庫使用者名稱欄位--> <entry key="username" value="loginname"/> </map> </property> <property name="resultAttributeMapping"> <map> <!--key為對應的資料庫欄位名稱,value為提供給客戶端獲取的屬性名字,系統會自動填充值--> <entry key="suid" value="suid"/> </map> </property> <!-- <property name="queryType"> <value>OR</value> </property> --> </bean>
切記:查詢出來的欄位名中間不能使用 _ (下劃線),否則獲取不到資料,如 cell_phone 需要 設定別名為 cellPhone.queryAttributeMapping是組裝sql用的查詢條件屬性,上述配置後,結合封裝成查詢sql就是select* from userinfo where loginname=#username#resultAttributeMapping是sql執行完畢後返回的結構屬性, key對應資料庫欄位,value對應客戶端獲取引數。如果要組裝多個查詢條件,需要加上下面這個,預設為AND.
二、修改serviceRegistryDao<property name="queryType"> <value>OR</value> </property>
deployerConfigContext.xml中的 beans中id為serviceRegistryDao的屬性 registeredServicesList。在 registeredServicesList中新增allowedAttributes屬性的值。列出的每個值,在客戶端就可以訪問了。
此步驟非常重要,可以看看org.jasig.cas.services.RegexRegisteredService的原始碼,其中的allowedAttributes是關鍵。<util:list id="registeredServicesList"> <bean class="org.jasig.cas.services.RegexRegisteredService" p:id="0" p:name="HTTP and IMAP" p:description="Allows HTTP(S) and IMAP(S) protocols" p:serviceId="^(http?|https?|imaps?)://.*" p:evaluationOrder="10000001"> <!-- 新增該屬性allowedAttributes --> <property name="allowedAttributes"> <list> <value>suid</value> </list> </property> </bean> </util:list>
三、修改casServiceValidationSuccess.jsp
WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp.在server驗證成功後,這個頁面負責生成與客戶端互動的xml資訊,在預設的casServiceValidationSuccess.jsp中,只包括使用者名稱,並不提供其他的屬性資訊,因此需要對頁面進行擴充套件。如下:
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:authenticationSuccess> <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].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> <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>
四、CAS Client獲取使用者資訊AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(); Map attributes = principal.getAttributes(); String email = attributes.get("suid");
補充:自定義SQL查詢使用者資訊
在步驟一,使用SQL比較單一,這樣我就發現了類org.jasig.services.persondir.support.jdbc.NamedParameterJdbcPersonAttributeDao這個類中有DataSource和sql.但是我發現使用這個類我無法使用自定義查詢SQL因為不能隨便自己來傳入引數,所以可以自己寫類實現類org.jasig.services.persondir.support.AbstractDefaultAttributePersonAttributeDao。具體程式碼如下:
package org.jasig.cas.myself.persondir.support; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; import org.jasig.services.persondir.IPersonAttributes; import org.jasig.services.persondir.support.AbstractDefaultAttributePersonAttributeDao; import org.jasig.services.persondir.support.CaseInsensitiveNamedPersonImpl; import org.jasig.services.persondir.support.IUsernameAttributeProvider; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Required; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import com.google.common.collect.Lists; public class MyNamedParameterJdbcPersonAttributeDao extends AbstractDefaultAttributePersonAttributeDao implements InitializingBean { private JdbcTemplate jdbcTemplate; private DataSource dataSource; private String sql; private IUsernameAttributeProvider usernameAttributeProvider; private Set<String> availableQueryAttributes = null; // default private Set<String> userAttributeNames = null; // default @Required public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Required public void setSql(String sql) { this.sql = sql; } @Required public void setUsernameAttributeProvider(IUsernameAttributeProvider usernameAttributeProvider) { this.usernameAttributeProvider = usernameAttributeProvider; } public void setAvailableQueryAttributes(Set<String> availableQueryAttributes) { this.availableQueryAttributes = Collections.unmodifiableSet(availableQueryAttributes); } @Required public void setUserAttributeNames(Set<String> userAttributeNames) { this.userAttributeNames = Collections.unmodifiableSet(userAttributeNames); } @Override public void afterPropertiesSet() throws Exception { jdbcTemplate = new JdbcTemplate(dataSource); } @Override public Set<String> getAvailableQueryAttributes() { return availableQueryAttributes; } @Override public Set<IPersonAttributes> getPeopleWithMultivaluedAttributes(Map<String, List<Object>> queryParameters) { String username = usernameAttributeProvider.getUsernameFromQuery(queryParameters); Map<String,List<Object>> mapOfLists = new HashMap<String,List<Object>>(); Object suid = jdbcTemplate.queryForObject(sql, new RowMapper<Object>() { @Override public Object mapRow(ResultSet paramResultSet, int paramInt) throws SQLException { // TODO Auto-generated method stub return paramResultSet.getObject("SUID"); }}, new Object[]{username,username,username}); if(null != suid){ mapOfLists.put("suid", Lists.newArrayList(suid)); } return getResults(username, mapOfLists); } @Override public Set<String> getPossibleUserAttributeNames() { return userAttributeNames; } public Set<IPersonAttributes> getResults(String username, Map<String, List<Object>> attributes) { IPersonAttributes person = new CaseInsensitiveNamedPersonImpl(username, attributes); return Collections.singleton(person); } }
然後在deployerConfigContext.xml檔案做如下修改:<bean id="attributeRepository" class="org.jasig.cas.myself.persondir.support.MyNamedParameterJdbcPersonAttributeDao"> <property name="dataSource" ref="dataSource"/> <property name="sql" value="select suid from login where NAME = ? or CELLPHONE = ? or EMAIL = ?" /> <property name="usernameAttributeProvider" ref="usernameAttributeProvider"/> <property name="userAttributeNames"> <value>suid</value> </property> </bean> <bean id="usernameAttributeProvider" class="org.jasig.services.persondir.support.SimpleUsernameAttributeProvider" /> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${cust.center.jdbc.driverClassName}" /> <property name="jdbcUrl" value="${cust.center.jdbc.url}" /> <property name="user" value="${cust.center.jdbc.username}" /> <property name="password" value="${cust.center.jdbc.password}" /> <property name="preferredTestQuery" value="SELECT 1"/> <property name="idleConnectionTestPeriod" value="18000"/> <property name="testConnectionOnCheckout" value="true"/> </bean>
這樣自定義查詢方式就完成了,也可以返回更多使用者資訊。當然我這是隻返回一個多餘資訊。如果你需要返回多個,可以做相應的修改哈。