轉載請聲明來自:http://www.cnblogs.com/chixinzei/p/7504272.html 謝謝!css
數據庫驗證,druid數據源,頁面定製,restful登陸,shibboleth idp saml2.0整合,根據帳號自動登陸服務器後跳轉子系統連接(webUtils修改的有問題,暫時不要參照此功能)html
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
取消cas https驗證:http://www.cnblogs.com/xiaojf/p/6617693.html
準備本地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"/>
簡單用戶名和密碼配置修改:/cas-server-support-x509/src/test/resources/deployerConfigContext.xml
casuser,Mellon。
另外此用戶名密碼也能夠配置在cas.properties中:
二者都存在時優先使用properties的配置
xml存在,而properties不存在時,則讀取xml。
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);
compile project(':cas-server-support-jdbc') compile group: 'mysql', name: 'mysql-connector-java', version: mysqlConnectorJavaVersion
關閉簡單用戶密碼校驗,改成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>
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
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—>
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<%m>%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<%m>%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>
4.複製css和js各一份出來,重命名爲上面的名字:
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
請求方式: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
請求方式: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
請求方式: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'>
未可以識別出目標 'ST-1-0wjyNMlwOK3kevi5Fa6w-www.casServer.com'票根
</cas:authenticationFailure>
</cas:serviceResponse>
<!-- ========================單點登陸開始 ======================== --> <!--用於單點退出,該過濾器用於實現單點登出功能,可選配置 --> <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> <!-- ========================單點登陸結束 ======================== -->
jdk8.0.131 tomcat8.5.16 cas-4.2.1(基於gradle) ,shibboleth-identityprovider-2.4.5,win7 64,mysql 5.6.21,花生殼免費域名映射(不然本身須要再下一個shibboleth的sp服務器用於測試)
tomcat必須配置ssl鏈接,此tomcat用於啓動cas-server和shib-idp(我一個是用idea啓動,ssl訪問端口設置不一樣而已),參考上面的《本地tomcat證書生成與配置》
參考文檔:https://wiki.shibboleth.net/confluence/display/SHIB2/IdPInstall
把cas的客戶端jar包(cas-client-core-3.4.1.jar)丟到idp解壓的lib目錄下,例如:D:\Program Files (x86)\shibboleth-identityprovider-2.4.5\lib,這樣安裝好的idp就會有這個jar包
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>
上面先告一段落,開始修改cas-server的整合部分:
參考:https://apereo.github.io/cas/4.2.x/integration/Shibboleth.html
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>
增長如下內容:
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>
3.3 cas-server增長saml2.0支持包 cas-server-support-saml-mdui
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-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>
參考教程:https://www.testshib.org/register.html
在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"/>
訪問測試sp地址:https://sp.testshib.org/,在修改下方的idp訪問地址,go!
注意:修改全在cas服務器端修改
添加用於區別自動登陸和正常登陸的帳號屬性
內容以下:
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(); } }
添加獲取在自動登陸後的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; }
添加根據自動登陸參數驗證的操做
@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); } }
添加根據自動登陸後把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); }
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); } } }
<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