1. 程式人生 > >CAS+Shiro實現許可權管理

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>