|
CAS多點(diǎn)登陸之非主流配置方式 場景想要用到的場景:用戶訪問WEB服務(wù),WEB訪問非WEB服務(wù)1,服務(wù)1又再訪問2、3,合并計(jì)算后,把數(shù)據(jù)返回給WEB及前端用戶。想讓訪問鏈上的所有服務(wù)都能得到認(rèn)證和鑒權(quán),認(rèn)為本次請求確實(shí)是來自用戶的。所以想到用CAS,讓用戶在一點(diǎn)登錄,所有服務(wù)都到此處認(rèn)證和鑒權(quán)。 CAS小介紹下面是兩張圖,來自網(wǎng)上兩個(gè)PPT(猛戳下載),其中一個(gè)還有動(dòng)畫演示,感謝原分享者。 用我的話解釋下就是: 1、 用戶先訪問http://adm/index.html服務(wù),因?yàn)闆]有登陸被重定向到CAS去輸入用戶名密碼,這個(gè)好理解。注意重定向地址: 2、 登陸完成后,CAS會(huì)寫一個(gè)COOKIE(CASTGC),它的作用是下次再認(rèn)證時(shí)不用再輸入密碼。同時(shí),CAS把用戶重定向回原來訪問地址: 3、 這時(shí)候,ADM這個(gè)WEB服務(wù),再用ticket去CAS做認(rèn)證,CAS報(bào)告OK,它即認(rèn)為用戶登陸了。 4、 如:用戶再訪問下一個(gè)AMS的WEB服務(wù)時(shí),因?yàn)閹в蠧ASTGC這個(gè)COOKIE,被重定向到CAS后,它就會(huì)用這個(gè)COOKIE直接生成一個(gè)ticket(就沒有讓用戶登陸的過程了哦?。珹MS拿到ticket后再去認(rèn)證就可以了。 開頭場景遇到的問題開始我們的場景如果全部照搬CAS的應(yīng)用,會(huì)存在如下問題: 1、 和CAS的交互全走HTTPS,要在JRE中生成和導(dǎo)入證書(網(wǎng)上搜配置tomcat的https一大把),用戶認(rèn)證時(shí)會(huì)被提示證書不可信。如果是一個(gè)直接交付給終端的產(chǎn)品,誰來配置這些東東?讓用戶看到這種提示又情何以堪? 2、 每當(dāng)訪問一個(gè)新服務(wù)都要和CAS產(chǎn)生兩次交互,申請簽發(fā)TICKET,再去認(rèn)證TICKET 3、 默認(rèn)的ticket有效時(shí)間很短,重定向回來后,要馬上去認(rèn)證,并且一個(gè)ticket只能去CAS認(rèn)證一次就失效了 4、 Ticket是和原始的URL綁定的,兩者都要提供給CAS才能認(rèn)證通過,即你不能用AMS服務(wù)簽發(fā)的ticket,去用在ADM服務(wù)的認(rèn)證上 5、 如果是非WEB的服務(wù)要認(rèn)證,需要用到CAS的代理模式,過程比較繁復(fù) 結(jié)論是開頭場景要用CAS是很艱難的。 變通后的方案這是我想到的一些改動(dòng)來滿足開頭場景: 1、 改為HTTP驗(yàn)證方式 2、 由WEB服務(wù)去CAS簽發(fā)一次TICKET,后繼的非WEB服務(wù)全部用這一個(gè)TICKET到CAS做認(rèn)證,它和用戶登陸后有效期一致,也沒有使用次數(shù)限制 3、 提供一個(gè)FILTER來為WEB層所有頁面統(tǒng)一提供認(rèn)證服務(wù) 4、 用戶名、密碼、鑒權(quán)信息(用戶角色)存到數(shù)據(jù)庫 下面就介紹這種非主流的改法,可能已經(jīng)安全性大大降低,但至少能RUN啦。。。 下載安裝下載并解壓CAS安裝包:(不要問為啥下載JASIG的,因?yàn)榫W(wǎng)上全是它。。) http://www./cas_server_3_5_1_release 解壓后帶源碼,后面步驟還會(huì)用到。 把其中modules目錄下的這個(gè)WAR包布到tomcat的webapp目錄,重啟下就算安裝好了: cas-server-3.5.1/modules/cas-server-webapp-3.5.1.war 改配置解決HTTP和TICKET生命期1、 加長TICKET的生命期和使用次數(shù): 2、 改為使用HTTP: 從JDBC認(rèn)證和鑒權(quán)1、 把默認(rèn)的用戶名密碼相同即通過的認(rèn)證方式注釋掉: 替換為下面這段從數(shù)據(jù)庫讀取: <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="sql" value="select passwd from t_admin where nickname=?"/> <property name="dataSource" ref="dataSource"/> </bean> 2、 把默認(rèn)的鑒權(quán)信息獲取方式注釋掉: 替換為: 這種是一個(gè)用戶僅一個(gè)角色(SingleRow): <bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao"> <constructor-arg index="0" ref="dataSource"/> <constructor-arg index="1" value="SELECT g.id,g.groupname,role.role FROM t_group AS g LEFT OUTER JOIN t_group_role AS grouprole ON (g.id = grouprole.groupid) LEFT OUTER JOIN t_role AS role ON (role.id = grouprole.roleid) LEFT OUTER JOIN t_group_user AS groupuser on (g.id = groupuser.groupid) LEFT OUTER JOIN t_admin ON (t_admin.id = groupuser.userid) WHERE t_admin.nickname = ?"/> <!--這里的key需寫username,value對應(yīng)數(shù)據(jù)庫用戶名字段 --> <property name="queryAttributeMapping"> <map> <entry key="username" value="nickname"/> </map> </property> <!--key對應(yīng)數(shù)據(jù)庫字段,value對應(yīng)客戶端獲取參數(shù) --> <property name="resultAttributeMapping"> <map> <entry key="role" value="authorities"/> </map> </property> </bean> 3、 多行模式(和上面的單行模式二選一) 這種是一個(gè)用戶對應(yīng)多個(gè)角色(MultiRow):(這里這個(gè)attr_name繞了我半天。。。這里有點(diǎn)解釋) <bean id="attributeRepositoryMulti" class="org.jasig.services.persondir.support.jdbc.MultiRowJdbcPersonAttributeDao"> <constructor-arg index="0" ref="dataSource"/> <constructor-arg index="1" value="SELECT g.id,g.groupname,'authorities' as attr_name,role.role FROM t_group AS g LEFT OUTER JOIN t_group_role AS grouprole ON (g.id = grouprole.groupid) LEFT OUTER JOIN t_role AS role ON (role.id = grouprole.roleid) LEFT OUTER JOIN t_group_user AS groupuser on (g.id = groupuser.groupid) LEFT OUTER JOIN t_admin ON (t_admin.id = groupuser.userid) WHERE t_admin.nickname = ?"/> <!--這里的key需寫username,value對應(yīng)數(shù)據(jù)庫用戶名字段 --> <property name="queryAttributeMapping"> <map> <entry key="username" value="nickname"/> </map> </property> <property name="nameValueColumnMappings"> <map> <entry key="attr_name" value="role" /> </map> </property> </bean> 如果要用多行模式把相應(yīng)的引用的類要變一下: 4、 鑒權(quán)信息要能輸出到前端,還要改下JSP: 在上圖所示位置加下: <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}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)-1}" step="1"> <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}> </c:forEach> </cas:attributes> </c:if> 5、 加上數(shù)據(jù)源定義 如下: <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/uu?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true"></property> <property name="username" value="root"></property> <property name="password" value="xxxxx"></property> </bean> 6、 建表:(表結(jié)構(gòu)來自此處) SET FOREIGN_KEY_CHECKS=0; ------------------------------ -- 創(chuàng)建管理員賬號表t_admin -- ---------------------------- CREATE TABLE `t_admin` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `passwd` varchar(12) NOT NULL DEFAULT '' COMMENT '用戶密碼', `nickname` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名字', `phoneno` varchar(32) NOT NULL DEFAULT '' COMMENT '電話號碼', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- 添加3個(gè)管理賬號 -- ---------------------------- INSERT INTO `t_admin` VALUES ('1', 'admin', 'admin', ''); INSERT INTO `t_admin` VALUES ('4', '123456', 'test', ''); INSERT INTO `t_admin` VALUES ('5', '111111', '111111', ''); -- ---------------------------- -- 創(chuàng)建權(quán)限表t_role -- ---------------------------- CREATE TABLE `t_role` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `role` varchar(40) NOT NULL DEFAULT '', `descpt` varchar(40) NOT NULL DEFAULT '' COMMENT '角色描述', `category` varchar(40) NOT NULL DEFAULT '' COMMENT '分類', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8; -- ---------------------------- -- 加入4個(gè)操作權(quán)限 -- ---------------------------- INSERT INTO `t_role` VALUES ('1', 'ROLE_ADMIN', '系統(tǒng)管理員', '系統(tǒng)管理員'); INSERT INTO `t_role` VALUES ('2', 'ROLE_UPDATE_FILM', '修改', '影片管理'); INSERT INTO `t_role` VALUES ('3', 'ROLE_DELETE_FILM', '刪除', '影片管理'); INSERT INTO `t_role` VALUES ('4', 'ROLE_ADD_FILM', '添加', '影片管理'); -- ---------------------------- -- 創(chuàng)建權(quán)限組表 -- ---------------------------- CREATE TABLE `t_group` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `groupname` varchar(50) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; -- ---------------------------- -- 添加2個(gè)權(quán)限組 -- ---------------------------- INSERT INTO `t_group` VALUES ('1', 'Administrator'); INSERT INTO `t_group` VALUES ('2', '影片維護(hù)'); -- ---------------------------- -- 創(chuàng)建權(quán)限組對應(yīng)權(quán)限表t_group_role -- ---------------------------- CREATE TABLE `t_group_role` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `groupid` bigint(20) unsigned NOT NULL, `roleid` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `groupid2` (`groupid`,`roleid`), KEY `roleid` (`roleid`), CONSTRAINT `t_group_role_ibfk_1` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`), CONSTRAINT `t_group_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8; -- ---------------------------- -- 加入權(quán)限組與權(quán)限的對應(yīng)關(guān)系 -- ---------------------------- INSERT INTO `t_group_role` VALUES ('1', '1', '1'); INSERT INTO `t_group_role` VALUES ('2', '2', '2'); INSERT INTO `t_group_role` VALUES ('4', '2', '4'); -- ---------------------------- -- 創(chuàng)建管理員所屬權(quán)限組表t_group_user -- ---------------------------- CREATE TABLE `t_group_user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `userid` bigint(20) unsigned NOT NULL, `groupid` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `userid` (`userid`), KEY `groupid` (`groupid`), CONSTRAINT `t_group_user_ibfk_2` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`), CONSTRAINT `t_group_user_ibfk_3` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8; -- ---------------------------- -- 將管理員加入權(quán)限組 -- ---------------------------- INSERT INTO `t_group_user` VALUES ('1', '1', '1'); INSERT INTO `t_group_user` VALUES ('2', '4', '2'); -- ---------------------------- -- 創(chuàng)建管理員對應(yīng)權(quán)限表t_user_role -- 設(shè)置該表可跳過權(quán)限組,為管理員直接分配權(quán)限 -- ---------------------------- CREATE TABLE `t_user_role` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `userid` bigint(20) unsigned NOT NULL, `roleid` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `userid` (`userid`), KEY `roleid` (`roleid`), CONSTRAINT `t_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`), CONSTRAINT `t_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; 與spring-security結(jié)合使用我們是自己開發(fā)filter,但順便把spring-security的配置方法帶一下: 1、 Web.xml加上spring-security的filter: <!-- spring 配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext-security-ns.xml</param-value> </context-param> <!-- spring 默認(rèn)偵聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <!-- Filter 定義 --> <!-- spring security filter --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 2、 application參考配置以及修改的vote文件(這里) 在單行模式下,權(quán)限信息可以正常解析到detail變量中,但在多行的時(shí)候,CAS傳過來的多個(gè)角色是這種格式:[ROLE_1,ROLE_2],帶有中括號,原有VOTE是精確比對,附件里改了個(gè)index函數(shù)來比對: 有了上面這些Spring-security就可以正常運(yùn)作,但是場景里要使用非WEB服務(wù)多次驗(yàn)證,所以其實(shí)不能用spring-security的filter,我們還是要自己寫的。 Go on… 解除TICKET與SERVICE的綁定,要改代碼哦!Cas服務(wù)器ticket和service驗(yàn)證時(shí),要和來簽發(fā)時(shí)的service url一致才行,否則就報(bào)下面的錯(cuò)誤: org.jasig.cas.client.validation.TicketValidationException: ticket 'ST-14-SYa99tdAMhI31ZehfSTW-cas01.example.org' does not match supplied service 為了我們多個(gè)不同服務(wù)可以重復(fù)使用一個(gè)ticket,CAS的源碼上做個(gè)小小改動(dòng)即可: 去到之前解壓的CAS目錄,搜出這句話的java文件: grep -R "does not match supplied service" . 修改這個(gè)文件: vi ./cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java 注釋掉驗(yàn)證服務(wù)URL的相應(yīng)行就好了: 編譯一下: mvn compile mvn -DskipTests=true package 把編好的: cas-server-3.5.1/cas-server-core/target/cas-server-core-3.5.1.jar 拷到下列位置替換原來的: /usr/local/apache-tomcat-7.0.32/webapps/cas/WEB-INF/lib/ 重啟下TOM的小貓就可以了。 自制的FILTER自己制作的filter要達(dá)到目標(biāo)是: 1、 在沒認(rèn)證時(shí)可以重定向 2、 重定向回來的時(shí)候,要去認(rèn)證并把ticket寫COOKIE 3、 有COOKIE時(shí),取出來直接去做認(rèn)證 Filter的一個(gè)比較清晰易懂的基礎(chǔ)介紹 一個(gè)講COOKIE的文章 最后是完整代碼:點(diǎn)我 Filter的代碼是CasFilter.java,相當(dāng)簡單,下面這段就是用CAS提供的客戶端去驗(yàn)證TICKET,因?yàn)閟ervice不驗(yàn)了,所以validate的第二個(gè)參數(shù)已經(jīng)不重要。 Attributes就是權(quán)限信息: 非WEB服務(wù)的認(rèn)證和鑒權(quán)我們開頭場景中的WEB服務(wù)在訪問后端時(shí),把TICKET信息也帶上,這樣后端的非WEB服務(wù)也可以用剛才提到的函數(shù)去認(rèn)證,并且獲取到權(quán)限信息,做自己的鑒權(quán)。 服務(wù)1再訪問服務(wù)2時(shí),也一樣帶上TICKET,這樣因?yàn)槲覀円呀?jīng)延長了TICKET有效次數(shù)和期限,它也不會(huì)過期。 但是,一旦用戶LOGOUT了,這個(gè)TICKET也就失效,此時(shí)所有服務(wù)都將驗(yàn)證不通過,WEB自然又會(huì)把用戶重定向到CAS去登陸了。 羅里八嗦講一大堆,用了好多歪門斜道,不值一提,只是為今后有個(gè)查閱的地方。 有興趣可以訪問下我的生活博客:qqmovie.qzone.com |
|
|