【原創】apereo cas 4.2從0開始

轉載請聲明來自:http://www.cnblogs.com/chixinzei/p/7504272.html   謝謝!css

目前已實現功能

數據庫驗證,druid數據源,頁面定製,restful登陸,shibboleth idp saml2.0整合,根據帳號自動登陸服務器後跳轉子系統連接(webUtils修改的有問題,暫時不要參照此功能)html

 

 

cas相關參考文檔

http://www.cnblogs.com/xwdreamer/archive/2011/11/10/2296939.html spring webflowjava

https://apereo.github.io/cas/4.2.x/ CAS官方文檔mysql

http://docs.spring.io/spring-webflow/docs/2.4.5.RELEASE/reference/html Spring webflow 官方文檔git

與Shibboleth IDP端 SAML2.0整合文檔:github

https://apereo.github.io/cas/4.2.x/integration/Shibboleth.html 官網cas和Shibboleth IDP 整合web

http://wwwcomy.iteye.com/blog/2236016 Shibboleth IDP安裝中文博客算法

https://wiki.shibboleth.net/confluence/display/SHIB2/IdPInstall Shibboleth IDP安裝spring


工做環境準備

運行基礎:jdk8.0.131,cas-server:4.2.1(基於gradle),cas-client:3.3.3(基於maven),win7 64,tomcat8.5,mysql 5.6.21,idea-2017.2sql

ssl配置相關準備

1.http訪問配置

取消cas https驗證:http://www.cnblogs.com/xiaojf/p/6617693.html

2.https訪問配置

準備本地tomcat證書生成與配置:文檔參考:http://www.bug315.com/article/412.htm

若要在本地使用SSL訪問cas,則只須要修改cas-server-webapp\src\main\resources\services\HTTPSandIMAPS-10000001.json的屬性tgc.secure=false便可,意思是cas的cookie是否只在ssl下生成(若是爲true,則登錄後cookie也不會存放登陸信息和票據,依然重定向到登陸界面)

默認jdk密匙庫口令:changeit

 

1)cmd 移動到tomcat目錄下,生成server key :

keytool -genkey -alias tomcat -keyalg RSA -storepass changeit -keystore server.keystore -validity 3600

解釋:

-alias 表示證書的別名,一個keystore文件中能夠存放多個alias。

-keyalg RSA 表示密鑰算法的名稱爲RSA算法

-keypass changeit 表示密鑰的口令是changeit

-storepass changeit 表示密鑰庫(生成的keystore文件)的密鑰是changeit

-keystore server.keystore 表示指定密匙庫的名稱

-validity 3600  表示證書有效期3600天,也就是大概10年

 

刪除證書命令:

先移動到jdk的密匙總庫目錄:

如:D:\Program Files\Java\jdk1.8.0_131\jre\lib\security

執行刪除jdk密匙庫證書:

keytool -delete -alias tomcat -keystore cacerts

刪除後能夠再次將證書導入jdk密匙庫

 

2)輸入參數:

名字和姓氏:149p874e84.51mypc.cn (域名)

組織單位名稱:149p874e84.51mypc.cn

組織名稱:149p874e84.51mypc.cn

所在城市:jinjiang

所在省:fujian

國家/地區代碼:zh

 

3)證書導入的JDK的證書信任庫中

keytool -export -trustcacerts -alias tomcat -file server.cer -keystore  server.keystore -storepass changeit 

 keytool -import -trustcacerts -alias tomcat -file server.cer -keystore  "jdk目錄/jre/lib/security/cacerts" -storepass changeit

4)修改tomcat server.xml增長https支持,不須要刪除原來的http鏈接

<!--開啓https鏈接,若是是非外部ide啓動,則keystoreFile配置爲server.keystore便可!--> 

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" 

maxThreads="150" scheme="https" secure="true" 

clientAuth="false" sslProtocol="TLS" 

keystoreFile="D:/Program Files (x86)/apache-tomcat-8.5.16(8086)/server.keystore" keystorePass="changeit"/>

3.支持配置多個驗證攔截器。

簡單用戶名和密碼配置修改:/cas-server-support-x509/src/test/resources/deployerConfigContext.xml

casuser,Mellon。

clipboard(5)

另外此用戶名密碼也能夠配置在cas.properties中:

clipboard(7)

二者都存在時優先使用properties的配置

xml存在,而properties不存在時,則讀取xml。


CAS服務端配置修改

⒈jdbc簡單查詢校驗:

 

1.1mysql建表:

CREATE TABLE `t_user` ( 

`id` bigint(15) NOT NULL COMMENT'主鍵', 

`account` varchar(30) DEFAULT NULL COMMENT'帳號', 

`password` varchar(255) DEFAULT NULL COMMENT'密碼', 

`valid` tinyint(1) DEFAULT NULL COMMENT '是否有效', 

PRIMARY KEY(`id`) 

) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

--插入1條數據: 

INSERT INTO `t_user` 

(`id`, 

`account`, 

`password`, 

`valid`) 

VALUES 

(1, 

'admin', 

'md5加密後的密碼', 

1);

 

1.2 cas-server-webapp\build.gradle 引入jdbc支持包依賴:

compile project(':cas-server-support-jdbc') 

compile group: 'mysql', name: 'mysql-connector-java', version: mysqlConnectorJavaVersion

 

 

1.3 deployerConfigContext.xml

關閉簡單用戶密碼校驗,改成jdbc校驗,密碼MD5加密,配合druid

 1 <!--修改登陸方式爲jdbc驗證,注意,須要註釋掉QueryAndEncodeDatabaseAuthenticationHandler的註解注入,有衝突-->
 2 <!--<alias name="acceptUsersAuthenticationHandler" alias="primaryAuthenticationHandler" />-->
 3 <alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler"/>
 4 <bean id="queryDatabaseAuthenticationHandler"
 5           class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
 6     <!--<property name="dataSource" ref="queryDatabaseDataSource"></property>-->
 7     <!--<property name="sql" value="select password from t_user where account=?"></property>-->
 8     <property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
 9 </bean>
10 <!-- 添加MD5密碼加密功能 -->
11 <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
12     <constructor-arg index="0">
13         <value>MD5</value>
14     </constructor-arg>
15 </bean>
16 
17 <!-- 數據源配置 -->
18 <alias name="dataSource" alias="queryDatabaseDataSource"/>
19 <!-- 阿里 druid 數據庫鏈接池 -->
20 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
21     <!-- 數據庫基本信息配置 -->
22     <property name="url" value="${cas.druid.database.url}"/>
23     <property name="username" value="${cas.druid.database.username}"/>
24     <property name="password" value="${cas.druid.database.password}"/>
25     <property name="driverClassName" value="${cas.druid.database.driverClassName}"/>
26     <property name="filters" value="${cas.druid.database.filters}"/>
27     <!-- 最大併發鏈接數 -->
28     <property name="maxActive" value="${cas.druid.database.maxActive}"/>
29     <!-- 初始化鏈接數量 -->
30     <property name="initialSize" value="${cas.druid.database.initialSize}"/>
31     <!-- 配置獲取鏈接等待超時的時間 -->
32     <property name="maxWait" value="${cas.druid.database.maxWait}"/>
33     <!-- 最小空閒鏈接數 -->
34     <property name="minIdle" value="${cas.druid.database.minIdle}"/>
35     <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
36     <property name="timeBetweenEvictionRunsMillis" value="${cas.druid.database.timeBetweenEvictionRunsMillis}"/>
37     <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
38     <property name="minEvictableIdleTimeMillis" value="${cas.druid.database.minEvictableIdleTimeMillis}"/>
39     <property name="validationQuery" value="${cas.druid.database.validationQuery}"/>
40     <property name="testWhileIdle" value="${cas.druid.database.testWhileIdle}"/>
41     <property name="testOnBorrow" value="${cas.druid.database.testOnBorrow}"/>
42     <property name="testOnReturn" value="${cas.druid.database.testOnReturn}"/>
43     <property name="maxOpenPreparedStatements" value="${cas.druid.database.maxOpenPreparedStatements}"/>
44     <!-- 打開 removeAbandoned 功能 -->
45     <property name="removeAbandoned" value="${cas.druid.database.removeAbandoned}"/>
46     <!-- 1800 秒,也就是 30 分鐘 -->
47     <property name="removeAbandonedTimeout" value="${cas.druid.database.removeAbandonedTimeout}"/>
48     <!-- 關閉 abanded 鏈接時輸出錯誤日誌 -->
49     <property name="logAbandoned" value="${cas.druid.database.logAbandoned}"/>
50     <property name="proxyFilters">
51         <list>
52             <ref bean="log-filter"/>
53         </list>
54     </property>
55 </bean>
56 <bean id="log-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
57     <property name="connectionLogEnabled" value="${cas.druid.database.connectionLogEnabled}"/>
58     <property name="statementLogEnabled" value="${cas.druid.database.statementLogEnabled}"/>
59     <property name="resultSetLogEnabled" value="${cas.druid.database.resultSetLogEnabled}"/>
60     <property name="statementExecutableSqlLogEnable" value="${cas.druid.database.statementExecutableSqlLogEnable}"/>
61 </bean>
View Code

 

1.4 cas.propertie中新增配置:

 1 #sql經過用戶名查詢密碼
 2 cas.jdbc.authn.query.sql=select password  from  t_user  where account=? and  valid=true
 3 
 4 #druid config by xbwu
 5 cas.druid.database.url=jdbc:mysql://localhost:3306/xbwudb?characterEncoding=utf8
 6 cas.druid.database.username=root
 7 cas.druid.database.password=root
 8 cas.druid.database.driverClassName=com.mysql.jdbc.Driver
 9 cas.druid.database.filters=stat,wall
10 cas.druid.database.maxActive=20
11 cas.druid.database.initialSize=1
12 cas.druid.database.maxWait=60000
13 cas.druid.database.minIdle=10
14 cas.druid.database.timeBetweenEvictionRunsMillis=60000
15 cas.druid.database.minEvictableIdleTimeMillis=300000
16 cas.druid.database.validationQuery=SELECT 'x'
17 cas.druid.database.testWhileIdle= true
18 cas.druid.database.testOnBorrow=false
19 cas.druid.database.testOnReturn=false
20 cas.druid.database.maxOpenPreparedStatements=20
21 cas.druid.database.removeAbandoned=true
22 cas.druid.database.removeAbandonedTimeout=1800
23 cas.druid.database.logAbandoned=true
24 
25 cas.druid.database.connectionLogEnabled=false
26 cas.druid.database.statementLogEnabled=false
27 cas.druid.database.resultSetLogEnabled=true
28 cas.druid.database.statementExecutableSqlLogEnable=true
View Code

 

1.5 web.xml新增druid控制檯:

 1 <!-- 鏈接池 啓用 Web 監控統計功能    start-->
 2 <filter>
 3     <filter-name> DruidWebStatFilter </filter-name>
 4     <filter-class> com.alibaba.druid.support.http.WebStatFilter </filter-class>
 5     <init-param >
 6         <param-name> exclusions </param-name>
 7         <param-value> *. js ,*. gif ,*. jpg ,*. png ,*. css ,*. ico ,/ druid /* </param-value>
 8     </init-param>
 9 </filter >
10 <filter-mapping>
11     <filter-name> DruidWebStatFilter </filter-name>
12     <url-pattern>/*</url-pattern>
13 </filter-mapping>
14 <servlet >
15     <servlet-name> DruidStatView </servlet-name>
16     <servlet-class> com.alibaba.druid.support.http.StatViewServlet </servlet-class>
17 </servlet >
18 <servlet-mapping>
19     <servlet-name> DruidStatView </servlet-name>
20     <url-pattern>/druid/console/*</url-pattern>
21 </servlet-mapping>
22 <!-- 鏈接池 啓用 Web 監控統計功能    end—>
View Code

 

1.6 cas-server-webapp\src\main\resources\log4j2.xml新增druid日誌配置

  1 <?xml version="1.0" encoding="UTF-8" ?>
  2 <!-- Specify the refresh internal in seconds. -->
  3 <!-- OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
  4 <Configuration monitorInterval="60">
  5     <Appenders>
  6         <Console name="console" target="SYSTEM_OUT">
  7             <PatternLayout pattern="%d %p [%c] - %n&lt;%m&gt;%n"/>
  8         </Console>
  9         <RollingFile name="file" fileName="../caslog/cas.log" append="true"
 10                      filePattern="../caslog/cas-%d{yyyy-MM-dd-HH}.log">
 11             <PatternLayout pattern="%d %p [%c] - %n&lt;%m&gt;%n"/>
 12             <Policies>
 13                 <OnStartupTriggeringPolicy />
 14                 <SizeBasedTriggeringPolicy size="10 MB"/>
 15                 <TimeBasedTriggeringPolicy />
 16             </Policies>
 17         </RollingFile>
 18         <RollingFile name="auditlogfile" fileName="../caslog/cas_audit.log" append="true"
 19                      filePattern="../caslog/cas_audit-%d{yyyy-MM-dd-HH}.log">
 20             <PatternLayout pattern="%d %p [%c] - %m%n"/>
 21             <Policies>
 22                 <OnStartupTriggeringPolicy />
 23                 <SizeBasedTriggeringPolicy size="10 MB"/>
 24                 <TimeBasedTriggeringPolicy />
 25             </Policies>
 26         </RollingFile>
 27         <RollingFile name="perfFileAppender" fileName="../caslog/perfStats.log" append="true"
 28                      filePattern="../caslog/perfStats-%d{yyyy-MM-dd-HH}.log">
 29             <PatternLayout pattern="%m%n"/>
 30             <Policies>
 31                 <OnStartupTriggeringPolicy />
 32                 <SizeBasedTriggeringPolicy size="10 MB"/>
 33                 <TimeBasedTriggeringPolicy />
 34             </Policies>
 35         </RollingFile>
 36     </Appenders>
 37     <Loggers>
 38         <AsyncLogger  name="org.jasig" level="debug" additivity="false" includeLocation="true">
 39             <AppenderRef ref="console"/>
 40             <AppenderRef ref="file"/>
 41         </AsyncLogger>
 42         <!--不配置append默認使用AsyncRoot的append-->
 43         <AsyncLogger  name="org.springframework" level="warn" />
 44         <AsyncLogger name="org.springframework.webflow" level="warn" />
 45         <AsyncLogger name="org.springframework.web" level="warn" />
 46         <AsyncLogger name="org.pac4j" level="warn" />
 47         <!--
 48         <AsyncLogger name="org.opensaml" level="debug" additivity="false">
 49             <AppenderRef ref="console"/>
 50             <AppenderRef ref="file"/>
 51         </AsyncLogger>
 52         <AsyncLogger name="org.ldaptive" level="debug" additivity="false">
 53             <AppenderRef ref="console"/>
 54             <AppenderRef ref="file"/>
 55         </AsyncLogger>
 56         <AsyncLogger name="com.hazelcast" level="debug" additivity="false">
 57             <AppenderRef ref="console"/>
 58             <AppenderRef ref="file"/>
 59         </AsyncLogger>
 60         -->
 61 
 62         <AsyncLogger name="perfStatsLogger" level="info" additivity="false" includeLocation="true">
 63             <AppenderRef ref="perfFileAppender"/>
 64         </AsyncLogger>
 65 
 66         <AsyncLogger name="org.jasig.cas.web.flow" level="info" additivity="true" includeLocation="true">
 67             <AppenderRef ref="file"/>
 68         </AsyncLogger>
 69         <AsyncLogger name="org.jasig.inspektr.audit.support" level="info" includeLocation="true">
 70             <AppenderRef ref="console"/>
 71             <AppenderRef ref="auditlogfile"/>
 72             <AppenderRef ref="file"/>
 73         </AsyncLogger>
 74         <!--druid鏈接池信息打印-->
 75         <AsyncLogger name="druid.sql.Statement" level="debug" additivity="false" includeLocation="true">
 76             <AppenderRef ref="console"/>
 77             <AppenderRef ref="file"/>
 78         </AsyncLogger>
 79         <!--<AsyncLogger name="druid.sql.DataSource" level="debug" additivity="false" includeLocation="true">
 80             <AppenderRef ref="console"/>
 81         </AsyncLogger>
 82         <AsyncLogger name="druid.sql.Connection" level="debug" additivity="false" includeLocation="true">
 83             <AppenderRef ref="console"/>
 84         </AsyncLogger>-->
 85         <!--<AsyncLogger name="druid.sql.ResultSet" level="debug" additivity="false" includeLocation="true">
 86             <AppenderRef ref="console"/>
 87         </AsyncLogger>-->
 88         <AsyncLogger name="druid.sql" level="error" additivity="false" includeLocation="true">
 89             <AppenderRef ref="console"/>
 90         </AsyncLogger>
 91         <!--根輸出配置,輸出等級-->
 92         <!-- OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 93         <!--若是上面的日誌輸出配置了appender,而且輸出等級跟根配置有重疊-->
 94         <!--好比上面配置debug,則二者的info級別以上日誌都會在相同的輸出appender中(console)輸出,也就是說控制檯會輸出2次-->
 95         <!--因此儘可能根輸出配置高一點,好比error等級-->
 96         <!--若是咱們確實有這種需求(不想遵循父類的Appender),能夠在自定義配置中加上additivity="false"參數-->
 97         <AsyncRoot level="info">
 98             <AppenderRef ref="console"/>
 99             <AppenderRef ref="file"/>
100         </AsyncRoot>
101     </Loggers>
102 </Configuration>
View Code

 

1.7 頁面樣式定製化修改配置:

1.7.1 複製cas-server-webapp\src\main\webapp\WEB-INF下的view文件夾一份,命名爲:view_custom

clipboard

1.7.2 修改cas-server-webapp\src\main\webapp\WEB-INF\cas.properties內容:

clipboard(1)

1.7.3 樣式文件-修改cas-server-webapp\src\main\resources\cas-theme-default.properties的內容:

clipboard(11)

4.複製css和js各一份出來,重命名爲上面的名字:

clipboard(6)

 

 

2.服務器支持restfull調用登陸

cas-server-webapp\build.gradle 增長依賴:compile project(':cas-server-support-rest')

cas-server-webapp\src\main\webapp\WEB-INF\cas.properties 加長一次性票據有效期:st.timeToKillInSeconds=20 20秒有效

cas單點登陸restfull請求登陸範例:

參考官方文檔:https://apereo.github.io/cas/5.1.x/protocol/REST-Protocol.html

2.1 請求獲取TGT

請求方式:POST

head設置:Content-Type: application/x-www-form-urlencoded

範例:{cas服務器地址}/cas/v1/tickets?username=battags&password=password&additionalParam1=paramvalue

demo: http://localhost:8080/cas/v1/tickets?username=admin&password=1234qwer

正確返回:

返回頭:

HTTP/1.1 201

Cache-Control: no-store

Location: http://localhost:8080/cas/v1/tickets/TGT-1-fHlelcwDXNYOiFpiiGwnTtyArXD4rjNdtelyBaBaBWWNHHHvav-www.casServer.com

Content-Type: text/html;charset=UTF-8

Content-Length: 376

Date: Mon, 07 Aug 2017 01:42:35 GMT

2.2 拿到TGT,請求一次性票據ST

請求方式:POST

head設置:Content-Type: application/x-www-form-urlencoded

範例:{cas服務器地址}/cas/v1/tickets/{TGT id}?service={form encoded parameter for the service url}

demo:http://localhost:8080/cas/v1/tickets/TGT-1-fHlelcwDXNYOiFpiiGwnTtyArXD4rjNdtelyBaBaBWWNHHHvav-www.casServer.com?service=http%3A%2F%2FcasTest02.com%3A8082%2F

正確返回:

返回頭:

HTTP/1.1 200

Cache-Control: no-store

Content-Disposition: inline;filename=f.txt

Content-Type: application/x-msdownload;charset=UTF-8

Content-Length: 43

Date: Mon, 07 Aug 2017 01:45:56 GMT

返回內容:

ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com

2.3 拿着一次性票據,請求服務器驗證

請求方式:GET

範例:{cas服務器地址}/cas/p3/serviceValidate?service={service url}&ticket={service ticket}

demo:http://localhost:8080/cas/p3/serviceValidate?ticket=ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com&service=http%3A%2F%2FcasTest02.com%3A8082%2F

正確返回:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

<cas:authenticationSuccess>

<cas:user>admin</cas:user>

<cas:attributes>

<cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>

<cas:isFromNewLogin>true</cas:isFromNewLogin>

<cas:authenticationDate>2017-08-07T10:09:02.521+08:00</cas:authenticationDate>

</cas:attributes>

</cas:authenticationSuccess>

</cas:serviceResponse>

一次性票據過時返回:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>

<cas:authenticationFailure code='INVALID_TICKET'>

未可以識別出目標 &#039;ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com&#039;票根

</cas:authenticationFailure>

</cas:serviceResponse>


CAS客戶端

1.單點登陸攔截器配置:web.xml

<!-- ========================單點登陸開始 ======================== -->
    <!--用於單點退出,該過濾器用於實現單點登出功能,可選配置 -->
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <!--該過濾器用於實現單點登出功能,可選配置。 -->
    <filter>
        <filter-name>CASSingle Sign OutFilter</filter-name>
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASSingle Sign OutFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>CASFilter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>http://localhost:8080/cas/login</param-value>
        </init-param>
        <init-param>
            <!--此地址是用於給cas服務器進行回調的域名和端口,注意,若是在這個回調地址後面配置了具體的路徑和參數,而非簡單的域名端口,好比:-->
            <!--http://casTest01.com:8081/aaa/bb.jsp?AA=123-->
            <!--1.參數AA=123在訪問任何客戶端地址時都會cas服務器被帶回來-->
            <!--2.具體路徑:/aaa/bb.jsp   只有在訪問客戶端簡單域名加端口時,cas服務器纔會將完整路徑返回:http://casTest01.com:8081/aaa/bb-->
            <param-name>serverName</param-name>
            <param-value>http://casTest01.com:8081</param-value>

            <!--此地址是用於給cas服務器進行絕對回調的地址,只要是被cas攔截,只回調地址必然是service所對應參數-->
            <!--<param-name>service</param-name>-->
            <!--<param-value>http://casTest01.com:8081/loginSuccess.do</param-value>-->
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CASFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--該過濾器負責對Ticket的校驗工做,必須啓用它 -->
    <filter>
        <filter-name>CASValidationFilter</filter-name>
        <filter-class>
            org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
        </filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>http://localhost:8080/cas</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://casTest01.com:8081</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CASValidationFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 該過濾器負責實現HttpServletRequest請求的包裹, 好比容許開發者經過HttpServletRequest的getRemoteUser()方法得到SSO登陸用戶的登陸名,可選配置。 -->
    <filter>
        <filter-name>CASHttpServletRequest WrapperFilter</filter-name>
        <filter-class>
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASHttpServletRequest WrapperFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 該過濾器使得開發者能夠經過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登陸名。 好比AssertionHolder.getAssertion().getPrincipal().getName()。 -->
    <filter>
        <filter-name>CASAssertion Thread LocalFilter</filter-name>
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CASAssertion Thread LocalFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- ========================單點登陸結束 ======================== -->

  


番外篇!--與shibboleth idp saml2.0 聯合配置

1.工做環境準備

1.1 運行基礎

jdk8.0.131 tomcat8.5.16 cas-4.2.1(基於gradle) ,shibboleth-identityprovider-2.4.5,win7 64,mysql 5.6.21,花生殼免費域名映射(不然本身須要再下一個shibboleth的sp服務器用於測試)

1.2 ssl配置

tomcat必須配置ssl鏈接,此tomcat用於啓動cas-server和shib-idp(我一個是用idea啓動,ssl訪問端口設置不一樣而已),參考上面的《本地tomcat證書生成與配置》

2. 安裝shibboleth-identityprovider-2.4.5

參考文檔:https://wiki.shibboleth.net/confluence/display/SHIB2/IdPInstall

2.1 添加cas client  包支持

把cas的客戶端jar包(cas-client-core-3.4.1.jar)丟到idp解壓的lib目錄下,例如:D:\Program Files (x86)\shibboleth-identityprovider-2.4.5\lib,這樣安裝好的idp就會有這個jar包

2.2 執行install.bat安裝idp

clipboard(4)

2.3 修改idp的元數據文件

D:\Program Files (x86)\shibboleth-idp-server\metadata\idp-metadata.xml

這裏面的幾個idp接口訪問端口要改一下,由於我用的花生殼因此端口號是指定好了的,把文件裏面的:8443所有替換成空串便可。

 注意,因爲默認該元數據文件生成的sope是二級域名scope,須要改爲完整的三級域名sope,不然testshib驗證不經過!

例如:

個人花生殼idp地址:149p874e84.51mypc.cn:11898

原始生成   idp-metadata.xml的scope:<shibmd:Scope regexp="false">51mypc.cn:11898</shibmd:Scope>

須要修改爲:<shibmd:Scope regexp="false">149p874e84.51mypc.cn:11898</shibmd:Scope>

 

3.cas-server所需配合部分

上面先告一段落,開始修改cas-server的整合部分:

參考:https://apereo.github.io/cas/4.2.x/integration/Shibboleth.html

3.1  修改idp安裝目錄配置文件

idp安裝目錄/conf/handler.xml

把裏面的ph:LoginHandler xsi:type="ph:RemoteUser"節點替換成如下內容:

<!-- Remote User handler for CAS support -->
    <ph:LoginHandler xsi:type="ph:RemoteUser">
        <ph:AuthenticationMethod>
            urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
          </ph:AuthenticationMethod>
        <ph:AuthenticationMethod>
            urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
          </ph:AuthenticationMethod>
    </ph:LoginHandler>

3.2 修改idp.war包的web.xml文件

 

增長如下內容:

 1 <!-- For CAS client support -->
 2 <context-param>
 3     <param-name>serverName</param-name>
 4     <param-value>${idp域名+端口}</param-value>
 5 </context-param>
 6 <!-- CAS client filters -->
 7 <filter>
 8     <filter-name>CAS Authentication Filter</filter-name>
 9     <filter-class>
10       org.jasig.cas.client.authentication.AuthenticationFilter
11   </filter-class>
12     <init-param>
13         <param-name>casServerLoginUrl</param-name>
14         <param-value>${cas服務器域名加端口加項目名}/login</param-value>
15     </init-param>
16 </filter>
17 <filter-mapping>
18     <filter-name>CAS Authentication Filter</filter-name>
19     <url-pattern>/Authn/RemoteUser</url-pattern>
20 </filter-mapping>
21 <filter>
22     <filter-name>CAS Validation Filter</filter-name>
23     <filter-class>
24     org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
25   </filter-class>
26     <init-param>
27         <param-name>casServerUrlPrefix</param-name>
28         <param-value>${cas服務器域名加端口加項目名}</param-value>
29     </init-param>
30     <init-param>
31         <param-name>redirectAfterValidation</param-name>
32         <param-value>true</param-value>
33     </init-param>
34 </filter>
35 <filter-mapping>
36     <filter-name>CAS Validation Filter</filter-name>
37     <url-pattern>/Authn/RemoteUser</url-pattern>
38 </filter-mapping>
39 <filter>
40     <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
41     <filter-class>
42     org.jasig.cas.client.util.HttpServletRequestWrapperFilter
43   </filter-class>
44 </filter>
45 <filter-mapping>
46     <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
47     <url-pattern>/Authn/RemoteUser</url-pattern>
48 </filter-mapping>
View Code

 

3.3 cas-server增長saml2.0支持包 cas-server-support-saml-mdui

 

3.4 cas-server-webapp的deployerConfigContext.xml添加

 1 <!--saml2.0支持-->
 2 <bean id="samlMetadataUIParserAction"
 3               class="org.jasig.cas.support.saml.web.flow.mdui.SamlMetadataUIParserAction"
 4               c:entityIdParameterName="entityId"
 5               c:metadataAdapter-ref="metadataAdapter"/>
 6 <!--元數據配置-->
 7 <bean id="metadataAdapter"
 8               class="org.jasig.cas.support.saml.web.flow.mdui.StaticMetadataResolverAdapter"
 9               c:metadataResources-ref="metadataResources"
10               p:refreshIntervalInMinutes="300"
11               p:requireValidMetadata="true" />
12 <!--元數據源配置,動態獲取idp上的元數據-->
13 <util:map id="metadataResources">
14     <entry key="${完整idp域名https訪問地址包括項目名}/entities/">
15         <bean class="org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain">
16             <property name="filters">
17                 <list />
18             </property>
19         </bean>
20     </entry>
21 </util:map>
22 <!--元數據過濾器配置-->
23 <bean id="metadataFilters"
24               class="org.opensaml.saml.metadata.resolver.filter.impl.MetadataFilterChain">
25     <property name="filters">
26         <list>
27             <!--
28                     <bean class="org.opensaml.saml.metadata.resolver.filter.impl.RequiredValidUntilFilter"
29                           c:maxValidity="0"  />
30                     -->
31             <bean class="org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter"
32                           c:engine-ref="trustEngine" p:requireSignature="false"  />
33         </list>
34     </property>
35 </bean>
36 <bean id="trustEngine"
37               class="org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine"
38               c:keyInfoResolver-ref="keyInfoResolver"
39               c:resolver-ref="credentialResolver" />
40 <bean id="keyInfoResolver"
41               class="org.opensaml.xmlsec.keyinfo.impl.BasicProviderKeyInfoCredentialResolver">
42     <constructor-arg name="keyInfoProviders">
43         <list>
44             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.RSAKeyValueProvider" />
45             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.DSAKeyValueProvider" />
46             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.DEREncodedKeyValueProvider" />
47             <bean class="org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider" />
48         </list>
49     </constructor-arg>
50 </bean>
51 <bean id="credentialResolver"
52               class="org.opensaml.security.credential.impl.StaticCredentialResolver"
53               c:credential-ref="credentialFactoryBean" />
54 <bean id="credentialFactoryBean"
55               class="net.shibboleth.idp.profile.spring.relyingparty.security.credential.BasicResourceCredentialFactoryBean"
56               p:publicKeyInfo="classpath:inc-md-pub.pem" ></bean>
View Code

 

3.5 修改cas-server-webapp的login-webflow.xml內容

在view-state id="viewLoginForm" 節點提交動做新增saml2.0校驗,修改大體以下:

<view-state id="viewLoginForm" view="casLoginView" model="credential">
    <binder>
        <binding property="username" required="true"/>
        <binding property="password" required="true"/>
        <!--
            <binding property="rememberMe" />
            -->
    </binder>
    <on-entry>
        <set name="viewScope.commandName" value="'credential'"/>
        <!--進入該視圖節點時,進行saml2.0元數據校驗-->
        <evaluate expression="samlMetadataUIParserAction" />
    </on-entry>
    <transition on="submit" bind="true" validate="true" to="realSubmit"/>
</view-state>

 

 

4.使用testshib做爲sp端免費測試

參考教程:https://www.testshib.org/register.html

4.1 將咱們的cas-server和idp.war都部署到開啓了ssl的tomcat上進行啓動吧!而後開啓咱們的花生殼內網映射!

clipboard(10)

4.2 開始上傳idp端元數據文件

 clipboard(12)

4.3 修改idp端的元數據提供者配置

在idp安裝目錄下找到D:\Program Files (x86)\shibboleth-idp-server\conf\relying-party.xml

在節點<metadata:MetadataProvider id="ShibbolethMetadata" xsi:type="metadata:ChainingMetadataProvider">內插入testshib的idp元數據提供,插入後是這樣子:

<metadata:MetadataProvider id="ShibbolethMetadata" xsi:type="metadata:ChainingMetadataProvider">
    <!-- Load the IdP's own metadata.  This is necessary for artifact support. -->
    <metadata:MetadataProvider id="IdPMD" xsi:type="metadata:FilesystemMetadataProvider"
                                   metadataFile="D:\Program Files (x86)\shibboleth-idp-server/metadata/idp-metadata.xml"
                                   maxRefreshDelay="P1D" />
    <!--test shib-->
    <metadata:MetadataProvider id="HTTPMetadataTESTSHIB"
                  xsi:type="metadata:FileBackedHTTPMetadataProvider"
                  backingFile="D:\Program Files (x86)\shibboleth-idp-server/metadata/testshib-providers.xml"
                  metadataURL="http://www.testshib.org/metadata/testshib-providers.xml"/>

4.4 開始測試

訪問測試sp地址:https://sp.testshib.org/,在修改下方的idp訪問地址,go!

 

111

clipboard(13)

clipboard(2)

clipboard(9)

clipboard(3)

 

 

帳號自動登陸後跳轉子系統(webUtils修改的有問題,暫時不要參照此功能)

注意:修改全在cas服務器端修改

修改涉及文件

1.UsernamePasswordCredential

添加用於區別自動登陸和正常登陸的帳號屬性

內容以下:

package org.jasig.cas.authentication;

import org.apache.commons.lang3.builder.HashCodeBuilder;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

/**
 * Credential for authenticating with a username and password.
 *
 * @author Scott Battaglia
 * @author Marvin S. Addison
 * @since 3.0.0
 */
public class UsernamePasswordCredential implements Credential, Serializable {

    /** Authentication attribute name for password. **/
    public static final String AUTHENTICATION_ATTRIBUTE_PASSWORD = "credential";

    private static final long serialVersionUID = -700605081472810939L;


    @NotNull
    @Size(min=1, message = "required.username")
    private String username;
    /**
     * 用於區別自動登陸用戶名
     */
    private String autoAccount;

    @NotNull
    @Size(min=1, message = "required.password")
    private String password;

    /** Default constructor. */
    public UsernamePasswordCredential() {}

    /**
     * Creates a new instance with the given username and password.
     *
     * @param userName Non-null user name.
     * @param password Non-null password.
     */
    public UsernamePasswordCredential(final String userName, final String password) {
        this.username = userName;
        this.password = password;
    }

    public final String getPassword() {
        return this.password;
    }


    public final void setPassword(final String password) {
        this.password = password;
    }


    public final String getUsername() {
        return this.username;
    }

    public final void setUsername(final String userName) {
        this.username = userName;
    }

    public final String getAutoAccount() {
        return autoAccount;
    }

    public final void setAutoAccount(String autoAccount) {
        this.autoAccount = autoAccount;
    }


    @Override
    public String getId() {
        return this.username;
    }

    @Override
    public String toString() {
        return this.username;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final UsernamePasswordCredential that = (UsernamePasswordCredential) o;

        if (password != null ? !password.equals(that.password) : that.password != null) {
            return false;
        }

        if (username != null ? !username.equals(that.username) : that.username != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(username)
                .append(password)
                .toHashCode();
    }

}

 

2.WebUtils的getTicketGrantingTicketId方法

添加獲取在自動登陸後的cookie參數,避免獲取不到致使沒法識別自動登陸

/**
     * Gets the ticket granting ticket id from the request and flow scopes.
     *
     * @param context the context
     * @return the ticket granting ticket id
     */
    public static String getTicketGrantingTicketId(
            @NotNull final RequestContext context) {
        final String tgtFromRequest = (String) context.getRequestScope().get("ticketGrantingTicketId");
        final String tgtFromFlow = (String) context.getFlowScope().get("ticketGrantingTicketId");
        String tgtFromCookie=null;
        Cookie[] cookies=getHttpServletRequest(context).getCookies();g
        if(cookies!=null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("TGC")){
                    tgtFromCookie= cookie.getValue();
                }
            }
        }
        if(tgtFromRequest!=null){
            return tgtFromRequest;
        }else if(tgtFromFlow!=null){
            return tgtFromFlow;
        }else if(tgtFromCookie!=null){
            return tgtFromCookie;
        }else{
            return null;
        }
//        return tgtFromRequest != null ? tgtFromRequest : tgtFromFlow;

    }

 

 

3.QueryDatabaseAuthenticationHandler(jdbc驗證類)的authenticateUsernamePasswordInternal方法

添加根據自動登陸參數驗證的操做

@Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {

        if (StringUtils.isBlank(this.sql) || getJdbcTemplate() == null ) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly");
        }

        if(credential.getAutoAccount()!=null){
            final String username = credential.getAutoAccount();
            try {
                final String encryptedPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
            } catch (final IncorrectResultSizeDataAccessException e) {
                if (e.getActualSize() == 0) {
                    throw new AccountNotFoundException(username + " not found with SQL query");
                } else {
                    throw new FailedLoginException("Multiple records found for " + username);
                }
            } catch (final DataAccessException e) {
                throw new PreventedException("SQL exception while executing query for " + username, e);
            }
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
        }else {
            final String username = credential.getUsername();
            final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
            try {
                final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
                if (!dbPassword.equals(encryptedPassword)) {
                    throw new FailedLoginException("Password does not match value on record.");
                }
            } catch (final IncorrectResultSizeDataAccessException e) {
                if (e.getActualSize() == 0) {
                    throw new AccountNotFoundException(username + " not found with SQL query");
                } else {
                    throw new FailedLoginException("Multiple records found for " + username);
                }
            } catch (final DataAccessException e) {
                throw new PreventedException("SQL exception while executing query for " + username, e);
            }
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
        }
    }

 

4.TicketGrantingTicketCheckAction(tgt驗證類)的doExecute方法

添加根據自動登陸後把cookie放回 login流程的requestScope操做,不然默認流程login會提示沒有登陸

@Override
    protected Event doExecute(final RequestContext requestContext) throws Exception {
        final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
        if (!StringUtils.hasText(tgtId)) {
            return new Event(this, NOT_EXISTS);
        }

        String eventId = INVALID;
        try {
            final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
            if (ticket != null && !ticket.isExpired()) {
                eventId = VALID;
                //有效則放入
                WebUtils.putTicketGrantingTicketInScopes(requestContext, tgtId);
            }
        } catch (final AbstractTicketException e) {
            logger.trace("Could not retrieve ticket id {} from registry.", e);
        }
        return new Event(this,  eventId);
    }

 

5.AutoLoginController自動登陸入口

package com.sbs.cas.controller;/**
 * @description
 * @autor xbwu on 2017/9/9.
 */

import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.*;
import org.jasig.cas.ticket.TicketGrantingTicket;
import org.jasig.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.CookieGenerator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;

/**
 * 自動登陸接口
 * @author xbwu
 * @create 2017-09-09 
 **/
@Controller(value = "/autoLogin")
public class AutoLoginController {

    protected final transient Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    @Qualifier(value = "centralAuthenticationService")
    private CentralAuthenticationService centralAuthenticationService;

    @Autowired
    @Qualifier(value = "ticketGrantingTicketCookieGenerator")
    private CookieGenerator ticketGrantingTicketCookieGenerator;
    @NotNull
    @Autowired(required=false)
    @Qualifier("defaultAuthenticationSystemSupport")
    private AuthenticationSystemSupport authenticationSystemSupport;

    @RequestMapping(value = "/checkLogin")
    public ModelAndView autoLogin(HttpServletRequest request,HttpServletResponse response,Model model) throws Exception{
        String systemUrl=request.getParameter("systemUrl");
        String autoAccount=request.getParameter("autoAccount");
        if(StringUtils.isBlank(autoAccount)){
            throw new Exception("參數錯誤");
        }
        bindTicketGrantingTicket(autoAccount,request,response);
        response.sendRedirect(systemUrl);
        return null;
    }


    protected void bindTicketGrantingTicket(String autoAccount,HttpServletRequest request, HttpServletResponse response){
        try {
            UsernamePasswordCredential credential = new UsernamePasswordCredential();
            credential.setAutoAccount(autoAccount);
            credential.setUsername(autoAccount);
            final AuthenticationContextBuilder builder = new DefaultAuthenticationContextBuilder(
                    this.authenticationSystemSupport.getPrincipalElectionStrategy());
            final AuthenticationTransaction transaction =
                    AuthenticationTransaction.wrap(credential);
            this.authenticationSystemSupport.getAuthenticationTransactionManager().handle(transaction,  builder);
            final AuthenticationContext authenticationContext = builder.build(null);
            final TicketGrantingTicket tgt = this.centralAuthenticationService.createTicketGrantingTicket(authenticationContext);
            ticketGrantingTicketCookieGenerator.addCookie(response, tgt.getId());
        } catch (Exception e){
            e.printStackTrace();
            logger.error("bindTicketGrantingTicket has exception.", e);
        }
    }
}

 

6.web.xml配置自動登陸路徑經過springmvc請求

<servlet-mapping>
        <servlet-name>cas</servlet-name>
        <url-pattern>/autoLogin/*</url-pattern>
    </servlet-mapping>

 

完工!個人本地測試路徑:http://localhost:8080/cas/autoLogin/checkLogin?systemUrl=http://casTest01.com:8081&autoAccount=admin

 

demo下載:https://code.aliyun.com/cas/cas-overlay

相關文章
相關標籤/搜索