CAS二次開發記錄

 

建立項目

下載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>
相關文章
相關標籤/搜索