下載cas4.1.10的源代碼,裏面有很是多的module,咱們使用cas-server-webapp來做爲模塊進行二次開發。java
本身建立一個項目,將cas-server-webapp拷貝相關文件過來,注意整理pom的依賴。web
咱們這裏數據源使用的是jndi的方式,因此修改 deployerConfigContext.xml 增長以下配置spring
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"><value>java:comp/env/jdbc/OracleXXXX</value></property> </bean>
同時,在webapp/META-INF/context.xml中配置對應的數據源信息sql
<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/OracleXXXX" auth="Container" type="javax.sql.DataSource" maxActive="10" maxIdle="5" maxWait="1000" logAbandoned="true" username="XXXX" password="XXXX" driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@192.168.0.1:1521:xxxx"/> </Context>
考慮到數據的操做,我封裝了一個DAO方法,順便定義一下數據庫
<bean id="casDao" class="com.xxx.xxx.sso.CasDao" p:dataSource-ref="dataSource" />
哪些系統可以使用cas,默認的定義在session
<bean id="serviceRegistryDao" class="org.jasig.cas.services.JsonServiceRegistryDao" c:configDirectory="${service.registry.config.location:classpath:services}" />
cas支持4中接入方式的定義:oracle
一、InMemoryServiceRegistryDaoImpl
二、JsonServiceRegistryDao
三、JpaServiceRegistryDaoImpl:
四、MongoServiceRegistryDaoapp
考慮到實際的業務場景,這部分的數據,確定是須要配置到數據庫中的,並且可以自動刷新,支持新增新的service時,可以作到不停機。webapp
因此咱們須要本身去實現,定義以下jsp
<bean id="serviceRegistryDao" class="com.xxxx.xxxx.sso.DbServiceRegistryDaoImpl" p:casDao-ref="casDao" />
public class DbServiceRegistryDaoImpl implements ServiceRegistryDao { @NotNull private CasDao casDao; public DbServiceRegistryDaoImpl() { } public void setCasDao(CasDao casDao) { this.casDao = casDao; } @Override public RegisteredService save(RegisteredService registeredService) { return null; } @Override public boolean delete(RegisteredService registeredService) { return false; } @Override public List<RegisteredService> load() { return casDao.findRegisteredService(); } @Override public RegisteredService findServiceById(long l) { return casDao.findRegisteredServiceByServiceId(l); } }
咱們定義一個類去繼承ServiceRegistryDao 主要去實現load和findServiceById的方法,暫時不實現delete和save。
主要是從數據庫獲取數據 返回對應的對象
public List<RegisteredService> findRegisteredService() { List<RegisteredService> registeredServices = jdbcTemplate.query("select * from T_SYSTEM ", new Object[] {}, new int[] {}, new RowMapper<RegisteredService>() { @Override public RegisteredService mapRow(ResultSet rs, int rowNum) throws SQLException { RegexRegisteredService registeredServices = new RegexRegisteredService(); registeredServices.setServiceId("^"+rs.getString("redirect_uri")+".*"); registeredServices.setName(rs.getString("systemname")); registeredServices.setDescription(rs.getString("systemdes")); registeredServices.setId(rs.getLong("SYSTEMID")); List<String> allowedAttributes = new ArrayList<String>(); allowedAttributes.add(rs.getString("systemcode")); registeredServices .setAttributeReleasePolicy(new ReturnAllowedAttributeReleasePolicy(allowedAttributes)); return registeredServices; } }); return registeredServices; }
RegisteredService是一個接口,有兩個實現類,咱們使用 RegexRegisteredService ,該實現類支持RegisteredService的正則匹配。
allowedAttributes 很重要,後面咱們將用它來過濾返回給接入服務的attribute。
登陸的方法是定義在 deployerConfigContext.xml 的
<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager"> <constructor-arg> <map> <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" /> <entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" /> </map> </constructor-arg> <property name="authenticationPolicy"> <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" /> </property> </bean>
其中primaryAuthenticationHandler控制的是登陸認證 ,primaryPrincipalResolver是返回的屬性
primaryAuthenticationHandler 修改以下
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler" p:dataSource-ref="dataSource" p:passwordEncoder-ref="MD5PasswordEncoder" p:sql="select PWD from T_USER where USERACCOUNT = ?" /> <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"> <constructor-arg index="0"> <value>MD5</value> </constructor-arg> </bean>
由於咱們認證相對比較簡單,這裏使用QueryDatabaseAuthenticationHandler去處理登陸時候的認證
主要是primaryPrincipalResolver的處理,這裏是處理的認證成功後,返回業務系統的用戶的數據,默認只返回了userId,咱們須要返回更多的數據,這裏本身定義一個類來處理
<bean id="primaryPrincipalResolver" class="com.xxx.xxx.sso.MyPrincipalResolver" p:casDao-ref="casDao" />
public class MyPrincipalResolver implements PrincipalResolver { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @NotNull private CasDao casDao; @Override public Principal resolve(Credential credential) { final MyUsernamePasswordCredentials usernamePasswordCredentials = (MyUsernamePasswordCredentials) credential; String userStr = ""; Map<String,Object> attr = new HashMap<String,Object>(); try { String username = usernamePasswordCredentials.getUsername(); userStr = username; User user = casDao.queryUser(username); for(Role role : user.getRoles()){ Map<String,Object> systemAttr = new HashMap<String,Object>(); //TODO 設置要返回給接入服務的屬性 attr.put(role.getSystemCode(), systemAttr); } return new SimplePrincipal(userStr, attr); } catch (Exception e) { logger.error("獲取用戶屬性異常{}",e); } return null; } @Override public boolean supports(Credential credential) { return true; } public void setCasDao(CasDao casDao) { this.casDao = casDao; } }
這裏描述一下業務場景
咱們常常遇到以下需求,N個系統接入CAS;
對於同一個用戶,N個系統可能有不一樣的屬性信息(好比針對每一個系統用戶的權限信息),每一個系統之間,這類信息應該作到隔離。
因此咱們在用戶登陸後,構造一個Map, 使用接入系統的代碼去作key,想一想前面咱們在構造registeredServices是,設置的
setAttributeReleasePolicy(new ReturnAllowedAttributeReleasePolicy(allowedAttributes));
cas會根據這個去過濾相關的屬性。
有人問 ,爲何要全取出來 ,而不是根據登陸系統的狀況,每次動態獲取呢?
能夠測試一下,登陸完A系統後,若是用戶直接點擊B系統,CAS是不會進入這個方法的。
修改對應的protocol模板,返回屬性,因爲咱們接入系統,使用的是 shiro cas那個包,支持的是2.0的協議,因此修改view/protocol/2.0/casServiceValidationSuccess.jsp
<%@ page session="false" contentType="application/xml; charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'> <cas:authenticationSuccess> <cas:user>${fn:escapeXml(principal.id)}</cas:user> <!-- 這段 --> <c:if test="${fn:length(assertion.primaryAuthentication.principal.attributes) > 0}"> <cas:attributes> <c:forEach var="attr" items="${assertion.primaryAuthentication.principal.attributes}"> <c:forEach var="systemAttr" items="${attr.value}"> <cas:${fn:escapeXml(systemAttr.key)}>${fn:escapeXml(systemAttr.value)}</cas:${fn:escapeXml(systemAttr.key)}> </c:forEach> </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(chainedAuthentications) > 0}"> <cas:proxies> <c:forEach var="proxy" items="${chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(chainedAuthentications)}" step="1"> <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy> </c:forEach> </cas:proxies> </c:if> </cas:authenticationSuccess> </cas:serviceResponse>