shiro是一個權限管理框架,將安全認證相關的功能抽取出來組成,使用shiro就可以非常快速的完成認證、授權等功能的開發,降低系統成本。爲了能夠爲多個系統提供統一認證入口,又使用了cas,而且二者都涉及到對session管理,所以需要集成。
cas基本協議過程:
基礎模式的SSO訪問流程步驟:
- 訪問服務:客戶端發送請求訪問應用系統提供的服務資源。
- 定向認證:客戶端重定向用戶請求到中心認證服務器。
- 用戶認證:用戶進行身份認證
- 發放票據:服務器會產生一個隨機的 Service Ticket 。
- 驗證票據: SSO 服務器驗證票據 Service Ticket 的合法性,驗證通過後,允許客戶端訪問服務。
- 傳輸用戶信息: SSO 服務器驗證票據通過後,傳輸用戶認證結果信息給客戶端。
cas認證時序圖
對於訪問受保護資源的每個 Web 請求,CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket,如果沒有,則說明當前用戶尚未登錄,於是將請求重定向到指定好的 CAS Server 登錄地址,並傳遞 Service (也就是要訪問的目的資源地址),以便登錄成功過後轉回該地址。用戶在第 3 步中輸入認證信息,如果登錄成功,CAS Server 隨機產生一個相當長度、唯一、不可僞造的 Service Ticket,並緩存以待將來驗證,之後系統自動重定向到 Service所在地址,併爲客戶端瀏覽器設置一個 Ticket Granted Cookie(TGC),CAS Client 在拿到Service 和新產生的 Ticket 過後,在第 5,6 步中與 CAS Server 進行身份合適,以確保 Service Ticket 的合法性。
在該協議中,所有與 CAS 的交互均採用 SSL 協議,確保,ST 和 TGC 的安全性。協議工作過程中會有 2 次重定向的過程,但是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對於用戶是透明的。Shiro CAS 認證流程
· 1 用戶首次訪問受保護 的資源;例如 http://casclient/security/view.do
· 2 由於未通過認證,Shiro首先把請求地址(http://casclient/security/view.do)緩存起來。
· 3然後跳轉到 CAS服務器進行登錄認證,在 CAS 服務端認證完成後需要返回到請求的 CAS 客戶端,因此在請求時,必須在參數中添加返回地址 ( 在 Shiro 中名爲 CAS Service)。 例如 http://casserver/login?service=http://casclient/shiro-cas
· 4由CAS服務器認證通過後,CAS 服務器爲返回地址添加ticket。例如http://casclient/shiro-cas?ticket=ST-4-BWMEnXfpxfVD2jrkVaLl-cas
· 5接下來,Shiro會校驗 ticket 是否有效。由於 CAS 客戶端不提供直接認證,所以 Shiro 會向 CAS 服務端發起 ticket 校驗檢查,只有服務端返回成功時,Shiro 才認爲認證通過。
· 6認證通過,進入授權檢查。Shiro授權檢查與前面提到的相同。
· 7 最後授權檢查通過,用戶正常訪問到 http://casclient/security/view.do。
項目中配置:
- Shiro在1.2.0的時候提供了對cas的集成。因此在項目中添加shiro-cas的依賴
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-cas</artifactId>
- <version>${shiro.version}</version>
- </dependency>
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
- http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd"
- default-lazy-init="true">
- <!-- spring 可支持註解 -->
- <context:annotation-config />
- <!-- 用於掃描其他的.properties配置文件 -->
- <bean id="propertyConfigurer"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:config/shiro-cas.properties</value>
- </list>
- </property>
- </bean>
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~配置sessionManager start~~~~~~~~~~~~~~~~~~~~~ -->
- <!-- 緩存管理器redis-start -->
- <!-- 自定義cacheManager -->
- <bean id="redisManager" class="com.tgb.itoo.authority.cache.RedisManager"></bean>
- <bean id="redisCache" class="com.tgb.itoo.authority.cache.RedisCache">
- <constructor-arg ref="redisManager"></constructor-arg>
- </bean>
- <!-- 自定義redisManager-redis -->
- <bean id="redisCacheManager" class="com.tgb.itoo.authority.cache.RedisCacheManager">
- <property name="redisManager" ref="redisManager" />
- </bean>
- <!-- 緩存管理器redis-end-李社河-2015年4月14日 -->
- <!-- session會話存儲的實現類 -->
- <bean id="redisShiroSessionDAO" class="com.tgb.itoo.authority.cache.RedisSessionDAO">
- <property name="redisManager" ref="redisManager" />
- </bean>
- <!-- sessionIdCookie的實現,用於重寫覆蓋容器默認的JSESSIONID -->
- <bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
- <!-- cookie的name,對應的默認是 JSESSIONID -->
- <constructor-arg name="name" value="SHAREJSESSIONID" />
- <!-- jsessionId的path爲 / 用於多個系統共享jsessionId -->
- <property name="path" value="/" />
- </bean>
- <!-- session管理器 -->
- <bean id="sessionManager"
- class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
- <!-- 設置全局會話超時時間,默認30分鐘(1800000) -->
- <property name="globalSessionTimeout" value="1800000" />
- <!-- 是否在會話過期後會調用SessionDAO的delete方法刪除會話 默認true -->
- <property name="deleteInvalidSessions" value="true" />
- <!-- 會話驗證器調度時間 -->
- <property name="sessionValidationInterval" value="1800000" />
- <!-- session存儲的實現 -->
- <property name="sessionDAO" ref="redisShiroSessionDAO" />
- <!-- sessionIdCookie的實現,用於重寫覆蓋容器默認的JSESSIONID -->
- <property name="sessionIdCookie" ref="sharesession" />
- <!-- 定時檢查失效的session -->
- <property name="sessionValidationSchedulerEnabled" value="true" />
- </bean>
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~配置sessionManager end~~~~~~~~~~~~~~~~~~~~~~~~ -->
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~配置securityManager start~~~~~~~~~~~~~~~~~~~ -->
- <span style="color:#ff0000;"><!-- 取得用戶的權限信息集合 -->
- <!-- shiro於數據交互的類 ,自己寫的類的實現-ShiroRealmBean自己重寫的類的實現 -->
- <bean id="shiroRealm" class="com.tgb.itoo.authority.service.ShiroRealmBean">
- <property name="defaultRoles" value="user"></property>
- <!-- 注入自己實現的類,授權的過程-PermissionManager是雲平臺重寫的授權的過程,用戶Id->角色->資源 -->
- <property name="casServerUrlPrefix" value="${casServerUrlPrefix}"></property>
- <property name="casService" value="${casService}"></property>
- </bean></span>
- <!-- 如果要實現cas的remember me的功能,需要用到下面這個bean,並設置到securityManager的subjectFactory中 -->
- <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
- <span style="color:#ff0000;"><!-- shiro管理核心類 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="realm" ref="shiroRealm"></property>
- <property name="sessionMode" value="http"></property>
- <property name="subjectFactory" ref="casSubjectFactory"></property>
- <property name="cacheManager" ref="redisCacheManager" />
- <property name="sessionManager" ref="sessionManager"></property>
- </bean></span>
- <!-- 保證實現shiro內部的生命週期函數bean的執行 -->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
- <!-- 開啓shiro的註解,需藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 -->
- <bean
- class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
- depends-on="lifecycleBeanPostProcessor"></bean>
- <bean
- class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
- <property name="securityManager" ref="securityManager" />
- </bean>
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~配置securityManager end~~~~~~~~~~ -->
- <span style="color:#ff0000;"><!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 配置shiroSecurityFilter start~~~~~~~~~~ -->
- <!-- shiro過濾器 start -->
- <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager"></property>
- <property name="loginUrl" value="${loginUrl}"></property>
- <property name="filters">
- <map></span>
- <span style="color:#ff0000;"><span class="comments"> <!--添加casFilter到shiroFilter --></span>
- <entry key="casFilter">
- <bean class="org.apache.shiro.cas.CasFilter">
- <!--配置驗證錯誤時的失敗頁面 /main 爲系統登錄頁面 -->
- <property name="failureUrl" value="/message.jsp" />
- <property name="successUrl" value="getSystemindex" />
- </bean>
- </entry>
- </map>
- </property>
- <!-- 過濾器鏈,請求url對應的過濾器 -->
- <property name="filterChainDefinitions">
- <value>
- /mobile_**/**=anon
- /message.jsp=anon
- /shiro-cas=casFilter
- <!-- /shirologout=logoutFilter -->
- /logout=logout
- /**=user
- </value>
- </property>
- </bean>
- <!-- shiro過濾器 end -->
- <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~配置shiroSecurityFilter end~~ --></span>
- </beans>
shiroRealmBean:負責授權
- package com.tgb.itoo.authority.service;
- import java.util.Iterator;
- import java.util.List;
- import org.apache.shiro.authz.AuthorizationInfo;
- import org.apache.shiro.authz.SimpleAuthorizationInfo;
- import org.apache.shiro.cas.CasRealm;
- import org.apache.shiro.subject.PrincipalCollection;
- /**
- *
- * @author hanyi
- *
- */
- public class ShiroRealmBean extends CasRealm {
- private ShiroBean permissionMgr;
- /**
- * 負責授權
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(
- PrincipalCollection principals) {
- String permissionName;
- try {
- //得到userCode
- SimpleAuthorizationInfo author = new SimpleAuthorizationInfo();
- String userCode = (String) principals.getPrimaryPrincipal();
- //通過自己寫的實現來得到用戶權限集合
- List<String> lstPermission = permissionMgr
- .queryUserPermission(userCode);
- Iterator<String> it = lstPermission.iterator();
- //遍歷權限集合添加到授權信息對象
- while (it.hasNext()) {
- permissionName = it.next().toString();
- author.addStringPermission(permissionName);
- }
- return author;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- public ShiroBean getPermissionMgr() {
- return permissionMgr;
- }
- public void setPermissionMgr(ShiroBean permissionMgr) {
- this.permissionMgr = permissionMgr;
- }
- }
說明:shiroRealmBean繼承了CasRealm,CasRealm又繼承了AuthorizingRealm。所以,
shiroRealmBean中具體寫了授權實現邏輯,而認證則調用了CasRealm中的方法
shiro-cas.properties文件
- <span style="font-size:18px;">loginUrl=http://192.168.22.246:8888/cas/login?service=http://localhost:8091/itoo-exam-template-web/shiro-cas
- successUrl=http://localhost:8091/itoo-exam-template-web/addTemplet
- casServerUrlPrefix=http://192.168.22.246:8888/cas
- casService=http://localhost:8091/itoo-exam-template-web/shiro-cas</span>
說明:
loginUrl:cas登錄頁面(帶有請求的受保護資源,用於返回時)
casServerUrlPrefix是CAS服務端地址。
casService是應用服務地址,用來接收CAS服務端票據。
沒有單點登錄情況下的話,登錄認證和授權認證默認在AuthorizingRealm的doGetAuthorizationInfo和doGetAuthenticationInfo中進行,所以我這裏是通過shiroDbRealm(繼承AuthorizingRealm的自定義類)覆寫doGetAuthorizationInfo和doGetAuthenticationInfo,實現自定義登錄認證和授權認證。
有單點登錄情況下,登錄認證是在casserver進行的,那麼執行流程是這樣的:用戶從 cas server登錄成功後,跳到cas client的CasRealm執行默認的doGetAuthorizationInfo和doGetAuthenticationInfo,此時doGetAuthenticationInfo做的工作是把登錄用戶信息傳遞給shiro,保持默認即可,而對於授權的處理,可以通過MyCasRealm(繼承CasRealm的自定義類)覆寫doGetAuthorizationInfo進行自定義授權認證。