CAS+Shiro實現許可權管理
本次Demo直接使用 Shiro——實現許可權控制demo思路(包含自定義標籤hasAnyPermission)中的Shiro許可權管理的Demo,可點選連結前往檢視:https://blog.csdn.net/fancheng614/article/details/83718096
在使用CAS+Shiro實現認證授權時,首先得在Shiro的demo中加入jar包:cas-client-core-3.2.1.jar、shiro-cas-1.3.2.jar。
然後需要在Shiro的Demo基礎上修改一些檔案。
CAS+Shiro的Demo下載(其中資料庫表介面點選檢視表結構檢視):原始碼下載
1、將原有的ShiroRealm.java換成MyCasRealm.java。
這兩個Realm主要區別是:ShiroRealm.java繼承了AuthorizingRealm類,MyCasRealm.java繼承了CasRealm類。
下面是MyCasRealm.java的程式碼,可以將https://download.csdn.net/download/fancheng614/10763926中的Shiro的Demo下載下來與ShiroRealm.java進行對比。
package com.mfc.realm; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cas.CasAuthenticationException; import org.apache.shiro.cas.CasRealm; import org.apache.shiro.cas.CasToken; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.CollectionUtils; import org.apache.shiro.util.StringUtils; import org.jasig.cas.client.authentication.AttributePrincipal; import org.jasig.cas.client.validation.Assertion; import org.jasig.cas.client.validation.TicketValidationException; import org.jasig.cas.client.validation.TicketValidator; import org.springframework.beans.factory.annotation.Autowired; import com.mfc.dao.TRolePopedomDao; import com.mfc.dao.TUserRoleDao; import com.mfc.dao.TuserDao; import com.mfc.entity.TRolePopedom; import com.mfc.entity.TUserRole; import com.mfc.entity.Tuser; public class MyCasRealm extends CasRealm { @Autowired private TuserDao tuserDao; @Autowired private TUserRoleDao tUserRoleDao; @Autowired private TRolePopedomDao tRolePopedomDao; // 授權的方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1.從PrincipalCollection中獲取登陸使用者的資訊 Object principal = principals.getPrimaryPrincipal(); // 2.利用登陸使用者的資訊來獲取當前使用者的角色和許可權(需要查詢資料庫) Set<String> roles = new HashSet<String>(); // 角色 Set<String> popedoms = new HashSet<String>(); // 許可權 Tuser tuser = (Tuser) principal; TUserRole tUserRole = new TUserRole(); tUserRole.setUserId(tuser.getUserId()); List<TUserRole> userRoles = tUserRoleDao.find(tUserRole); for (TUserRole tUserRole2 : userRoles) { // 賦予使用者角色 roles.add(tUserRole2.getRoleId()); // 查詢角色下面的許可權,並將查詢出來的許可權賦給當前登陸使用者 TRolePopedom popedom = new TRolePopedom(); popedom.setRoleId(tUserRole2.getRoleId()); List<TRolePopedom> rolePopedoms = tRolePopedomDao.find(popedom); for (TRolePopedom tRolePopedom : rolePopedoms) { popedoms.add(tUserRole2.getRoleId() + ":" + tRolePopedom.getPopedomId()); } } // 3.建立SimpleAuthorizationInfo物件,並設定其roles SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); info.setStringPermissions(popedoms); info.addStringPermissions(popedoms); // 4.返回SimpleAuthorizationInfo物件 return info; } // 認證的方法 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { Subject currentUser = SecurityUtils.getSubject(); CasToken casToken = (CasToken) token; if (token == null) { return null; } String ticket = (String)casToken.getCredentials(); if (!StringUtils.hasText(ticket)) { return null; } TicketValidator ticketValidator = ensureTicketValidator(); try { // contact CAS server to validate service ticket Assertion casAssertion = ticketValidator.validate(ticket, getCasService()); // get principal, user id and attributes AttributePrincipal casPrincipal = casAssertion.getPrincipal(); String userId = casPrincipal.getName(); Map<String, Object> attributes = casPrincipal.getAttributes(); // refresh authentication token (user id + remember me) casToken.setUserId(userId); String rememberMeAttributeName = getRememberMeAttributeName(); String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName); boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue); if (isRemembered) { casToken.setRememberMe(true); } Tuser tuser = new Tuser(); tuser.setUserName(userId); Tuser loginUser = tuserDao.find(tuser).get(0); SecurityUtils.getSubject().getSession().setAttribute("loginUser", loginUser); // create simple authentication info List<Object> principals = CollectionUtils.asList(loginUser, attributes); PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName()); return new SimpleAuthenticationInfo(principalCollection, ticket); } catch (TicketValidationException e) { throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e); } } }
2、修改了spring-shiro.xml配置檔案,並添加了shiro.properties檔案。
注意:在專案中有一個shiro.properties檔案,還有一個config.properties配置檔案,此時需要在總的xml中直接使用 <context:property-placeholder location="classpath:*.properties"/>引入到xml中,儘量不要分兩次引用,專案啟動會報錯。
在applicationContext.xml中引入properties檔案:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <context:property-placeholder location="classpath:*.properties"/> <!-- 啟用spring註解 --> <context:annotation-config></context:annotation-config> <context:component-scan base-package="com.mfc"></context:component-scan> <!-- 引入配置hibernate的配置檔案 --> <import resource="classpath*:spring-dao.xml"/> <!-- 引入配置Shiro的配置檔案 --> <import resource="classpath*:spring-shiro.xml"/> </beans>
修改後的spring-shiro.xml檔案,可以與shiro的Demo中的spring-shiro.xml進行對比:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!--
1、配置securityManager
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="subjectFactory" ref="casSubjectFactory"></property>
<property name="sessionManager" ref="sessionManager"></property>
<property name="realm" ref="jdbcRealm"></property>
</bean>
<!--
2、配置快取
-->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!--
3、配置Realm
3.1、直接配置實現了org.apache.shiro.realm.Realm介面的bean
-->
<bean id="jdbcRealm" class="com.mfc.realm.MyCasRealm">
<!-- cas服務端地址字首 -->
<property name="casServerUrlPrefix" value="${shiro.casServerUrlPrefix}" />
<!-- 應用服務地址,用來接收cas服務端票據 ,這個路徑在本系統內“http://127.0.0.1:8080/CAS-ShiroPermission/”後面可以隨便加
但是filterChainDefinitions裡使用casFilter攔截器時,必須攔截/cas
-->
<property name="casService" value="${shiro.casService}" />
</bean>
<!--
4、配置lifecycleBeanPostProcessor,可以自動的來呼叫配置在Spring IOC 容器中的shiro bean的生命週期方法
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--
5、啟用IOC容器中使用shiro的註解。但必須在配置了lifecycleBeanPostProcessor之後才可以使用
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--
6、配置shiroFilter
6.1、id必須和web.xml中的配置的DelegatingFilterProxy的<filter-name>的值一致
若不一致會丟擲異常NoSuchBeanDefinitionException。因為shiro會來IOC容器中查詢和<filter-name>名字對應的filter bean
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 和casService值一樣 -->
<property name="loginUrl" value="${shiro.loginURL}"/>
<!-- 登入成功跳的地址 -->
<property name="successUrl" value="${shiro.successURL}" />
<property name="filters">
<map>
<!-- 新增casFilter到ShiroFilter -->
<entry key="casFilter" value-ref="casFilter"></entry>
<entry key="logoutFilter" value-ref="logoutFilter"></entry>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/cas= casFilter
/unauthorized.jsp = anon
/css/** = anon
/images/** = anon
/js/** = anon
/tuserCtrl/logout = logoutFilter
/** = authc
</value>
</property>
</bean>
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<!-- 配置驗證錯誤時的失敗頁面 -->
<property name="failureUrl" value="${shiro.failureUrl}"></property>
</bean>
<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<!-- 配置登出後跳往的頁面 ,可以和casService的值一樣,也可以不一樣了,可以在“http://127.0.0.1:8080/CAS-ShiroPermission/”後面隨便加-->
<property name="redirectUrl" value="${shiro.redirectUrl}" />
</bean>
<!-- 如果要實現cas的remember me的功能,需要用到下面這個bean,並設定到securityManager的subjectFactory中 -->
<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory"></bean>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"></property>
<property name="arguments" ref="securityManager"></property>
</bean>
<!-- session管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 超時時間 -->
<property name="globalSessionTimeout" value="1800000"/>
<!-- session儲存的實現 -->
<property name="sessionDAO" ref="shiroSessionDao"/>
<!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID -->
<property name="sessionIdCookie" ref="sharesession"/>
<!-- 定時檢查失效的session -->
<property name="sessionValidationSchedulerEnabled" value="true" />
</bean>
<!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID -->
<bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,對應的預設是 JSESSIONID -->
<constructor-arg name="name" value="SHAREJSESSIONID"/>
<!-- 記住我cookie生效時間30天 -->
<property name="maxAge" value="2592000" />
</bean>
<!-- session儲存的實現 -->
<bean id="shiroSessionDao" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />
</beans>
shiro.properties檔案(更詳細的說明還需要自己搭建專案體會):
#CAS中央伺服器的登入地址
shiro.casServerUrlPrefix=http://127.0.0.1:8081/CAS
#這個是客戶端接受CAS返回票據的地址,其中後面的/cas可以隨便寫(不需要是專案中某個請求的實際地址),
#但是/cas必須被spring-shiro.xml中的casFilter攔截器攔截
shiro.casService=http://127.0.0.1:8080/CAS-ShiroPermission/cas
#登入的URL
shiro.loginURL=http://127.0.0.1:8081/CAS/login?service=http://127.0.0.1:8080/CAS-ShiroPermission/cas
#登入成功後回撥的URL
shiro.successURL=http://127.0.0.1:8080/CAS-ShiroPermission/tmenuCtrl/menuList
#登入失敗的URL
shiro.failureUrl=/unauthorized.jsp
#登出後重定向的URL,這裡的http://127.0.0.1:8080/CAS-ShiroPermission/cas中的/cas也可以隨便寫,
#但是必須被spring-shiro.xml中的logoutFilter攔截器攔截
shiro.redirectUrl=http://127.0.0.1:8081/CAS/logout?service=http://127.0.0.1:8080/CAS-ShiroPermission/cas
到此,CAS+Shiro搭建完成。
3、修改CAS登入介面和登入成功的介面
如果想修改CAS的登入介面和登入成功介面,可以在CAS專案中的CAS/WebContent/WEB-INF/view/jsp/default/ui/路徑下找到對應的頁面進行修改。其中casLoginView.jsp是登入介面,casGenericSuccess.jsp是登入成功介面。
當然如果想直接登入中央伺服器,登入成功後在CAS的登入成功介面點選連結跳往其他子系統,可以直接在casGenericSuccess.jsp登入成功介面寫超連結跳轉,超連結記得帶上jsessionid,避免需要二次登入。
如:在casGenericSuccess.jsp中加入:
<a href="http://127.0.0.1:8080/CAS-ShiroPermission/tmenuCtrl/menuList;jsessionid=<%=request.getSession().getId()%>">Shiro許可權系統</a>