1. 程式人生 > >shiro安全框架擴充套件教程--整合cas框架擴充套件自定義CasRealm

shiro安全框架擴充套件教程--整合cas框架擴充套件自定義CasRealm

       這次我給大家講講如何在shiro中整合cas框架,以及擴充套件自定義的角色和資源體系,囉嗦話不多說了,直接上程式碼說明

第一步,搭建cas伺服器,我也不說拉,這個大家用現有的cas服務就行了

第二步,先加入cas-client的包到我們的專案,然後再下載個shiro-cas.jar也放到專案裡

第三步配置shiro中的cas設定

<description>shiro配置</description>

	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="cacheManager" ref="shiroCacheManager" />
		<property name="sessionManager" ref="sessionManager" />
		<property name="realm" ref="casRealm" />
		<property name="subjectFactory" ref="casSubjectFactory" />
		<!-- <property name="realm" ref="simpleUserRealm" /> -->
	</bean>

	<!-- 會話管理器 -->
	<bean id="sessionManager"
		class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<property name="sessionValidationSchedulerEnabled" value="false" />
		<property name="sessionDAO" ref="sessionDAO" />
		<property name="globalSessionTimeout" value="600000" />
	</bean>

	<!-- 快取管理器 -->
	<bean id="shiroCacheManager"
		class="com.silvery.security.shiro.cache.SimpleShiroCacheManager">
		<property name="cache" ref="shiroCache" />
	</bean>

	<!-- 快取實現類,注入自定義快取機制 -->
	<bean id="shiroCache" class="com.silvery.security.shiro.cache.SimpleShiroCache">
		<property name="cacheManager" ref="simpleCacheManager" />
	</bean>

	<!-- 會話讀寫實現類 -->
	<bean id="sessionDAO" class="com.silvery.security.shiro.session.CacheSessionDAO" />

	<!-- 使用者認證實現 -->
	<bean id="simpleUserRealm" class="com.silvery.security.shiro.realm.SimpleUserRealm" />

	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

	<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
		<!-- 配置驗證錯誤時的失敗頁面 -->
		<property name="failureUrl"
			value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
	</bean>

	<bean id="casRealm" class="com.silvery.security.shiro.realm.SimpleCasRealm">
		<property name="defaultRoles" value="ROLE_USER" />
		<property name="casServerUrlPrefix" value="https://cas.test.com:8443" />
		<!-- 客戶端的回撥地址設定,必須和下面的過濾器攔截的地址一致 -->
		<property name="casService" value="http://test.com/mh/cas/login.do" />
	</bean>

	<!-- 如果要實現cas的remember me的功能,需要用到下面這個bean,並設定到securityManager的subjectFactory中 -->
	<bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />

	<bean
		class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
		<property name="staticMethod"
			value="org.apache.shiro.SecurityUtils.setSecurityManager" />
		<property name="arguments" ref="securityManager" />
	</bean>

	<!-- 過濾鏈配置 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<property name="loginUrl"
			value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />
		<property name="filters">
			<map>
				<entry key="cas" value-ref="casFilter" />
				<entry key="role">
					<bean
						class="com.silvery.security.shiro.filter.SimpleRoleAuthorizationFilter" />
				</entry>
				<entry key="authc">
					<bean
						class="com.silvery.security.shiro.filter.SimpleFormAuthenticationFilter" />
				</entry>
				<entry key="exec">
					<bean class="com.silvery.security.shiro.filter.SimpleExecutiveFilter" />
				</entry>
			</map>
		</property>
	</bean>

	<!-- 許可權資源配置 -->
	<bean id="filterChainDefinitionsService"
		class="com.silvery.security.shiro.service.ini.impl.SimpleFilterChainDefinitionsService">
		<property name="definitions">
			<value>
				/mh/cas/login.do = cas
				/mh/casUrl.do = role[ROLE_USER]
				/static/** = anon
				/** = exec
			</value>
		</property>
	</bean>


關於這一個步驟的配置裡面註釋寫的比較清楚了,至於一些類是自己重寫的,可以自己參考前面的文章,/mh/cas/login.do其實就是cas攔截器的指定路徑,如果想登入就請求這個路徑即可,如果沒有登入他會跳轉cas的login頁面

第四步就是需要重寫我們的casrealm,你可以看到上面的配置有SimpleCasRealm,這個類是我自己重寫的,是為了方便分配自己本地系統的許可權體系,因為shiro-cas提供的預設CasRealm功能比較有限,不能動態角色體系,下面可以看看這個原始的CasRealm原始碼

public class CasRealm extends AuthorizingRealm
{

    public CasRealm()
    {
        validationProtocol = "CAS";
        rememberMeAttributeName = "longTermAuthenticationRequestTokenUsed";
        setAuthenticationTokenClass(org/apache/shiro/cas/CasToken);
    }

    protected void onInit()
    {
        super.onInit();
        ensureTicketValidator();
    }

    protected TicketValidator ensureTicketValidator()
    {
        if(ticketValidator == null)
            ticketValidator = createTicketValidator();
        return ticketValidator;
    }

    protected TicketValidator createTicketValidator()
    {
        String urlPrefix = getCasServerUrlPrefix();
        if("saml".equalsIgnoreCase(getValidationProtocol()))
            return new Saml11TicketValidator(urlPrefix);
        else
            return new Cas20ServiceTicketValidator(urlPrefix);
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
        throws AuthenticationException
    {
        CasToken casToken = (CasToken)token;
        if(token == null)
            return null;
        String ticket = (String)casToken.getCredentials();
        if(!StringUtils.hasText(ticket))
            return null;
        TicketValidator ticketValidator = ensureTicketValidator();
        try
        {
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();
            log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] {
                ticket, getCasServerUrlPrefix(), userId
            });
            Map attributes = casPrincipal.getAttributes();
            casToken.setUserId(userId);
            String rememberMeAttributeName = getRememberMeAttributeName();
            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
            if(isRemembered)
                casToken.setRememberMe(true);
            List principals = CollectionUtils.asList(new Object[] {
                userId, attributes
            });
            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
            return new SimpleAuthenticationInfo(principalCollection, ticket);
        }
        catch(TicketValidationException e)
        {
            throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(), e);
        }
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
    {
        SimplePrincipalCollection principalCollection = (SimplePrincipalCollection)principals;
        List listPrincipals = principalCollection.asList();
        Map attributes = (Map)listPrincipals.get(1);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        addRoles(simpleAuthorizationInfo, split(defaultRoles));
        addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
        List attributeNames = split(roleAttributeNames);
        String value;
        for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addRoles(simpleAuthorizationInfo, split(value)))
        {
            String attributeName = (String)i$.next();
            value = (String)attributes.get(attributeName);
        }

        attributeNames = split(permissionAttributeNames);
        String value;
        for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addPermissions(simpleAuthorizationInfo, split(value)))
        {
            String attributeName = (String)i$.next();
            value = (String)attributes.get(attributeName);
        }

        return simpleAuthorizationInfo;
    }

    private List split(String s)
    {
        List list = new ArrayList();
        String elements[] = StringUtils.split(s, ',');
        if(elements != null && elements.length > 0)
        {
            String arr$[] = elements;
            int len$ = arr$.length;
            for(int i$ = 0; i$ < len$; i$++)
            {
                String element = arr$[i$];
                if(StringUtils.hasText(element))
                    list.add(element.trim());
            }

        }
        return list;
    }

    private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles)
    {
        String role;
        for(Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
            role = (String)i$.next();

    }

    private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions)
    {
        String permission;
        for(Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo.addStringPermission(permission))
            permission = (String)i$.next();

    }

    public String getCasServerUrlPrefix()
    {
        return casServerUrlPrefix;
    }

    public void setCasServerUrlPrefix(String casServerUrlPrefix)
    {
        this.casServerUrlPrefix = casServerUrlPrefix;
    }

    public String getCasService()
    {
        return casService;
    }

    public void setCasService(String casService)
    {
        this.casService = casService;
    }

    public String getValidationProtocol()
    {
        return validationProtocol;
    }

    public void setValidationProtocol(String validationProtocol)
    {
        this.validationProtocol = validationProtocol;
    }

    public String getRememberMeAttributeName()
    {
        return rememberMeAttributeName;
    }

    public void setRememberMeAttributeName(String rememberMeAttributeName)
    {
        this.rememberMeAttributeName = rememberMeAttributeName;
    }

    public String getDefaultRoles()
    {
        return defaultRoles;
    }

    public void setDefaultRoles(String defaultRoles)
    {
        this.defaultRoles = defaultRoles;
    }

    public String getDefaultPermissions()
    {
        return defaultPermissions;
    }

    public void setDefaultPermissions(String defaultPermissions)
    {
        this.defaultPermissions = defaultPermissions;
    }

    public String getRoleAttributeNames()
    {
        return roleAttributeNames;
    }

    public void setRoleAttributeNames(String roleAttributeNames)
    {
        this.roleAttributeNames = roleAttributeNames;
    }

    public String getPermissionAttributeNames()
    {
        return permissionAttributeNames;
    }

    public void setPermissionAttributeNames(String permissionAttributeNames)
    {
        this.permissionAttributeNames = permissionAttributeNames;
    }

    public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
    public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
    private static Logger log = LoggerFactory.getLogger(org/apache/shiro/cas/CasRealm);
    private String casServerUrlPrefix;
    private String casService;
    private String validationProtocol;
    private String rememberMeAttributeName;
    private TicketValidator ticketValidator;
    private String defaultRoles;
    private String defaultPermissions;
    private String roleAttributeNames;
    private String permissionAttributeNames;

}

其實跟我們普通用的UserRealm或者是JdbcRealm差別不大,但是裡面增加了casToken的驗證,所以我們應該直接拿過來用,下面再加載出我們的自己的邏輯即可,所以我們可以選擇繼承當前的CasRealm過載一下他的兩個方法
/**
 * 
 * 擴充套件CAS橋接器,訂製角色體系和資源體系
 * 
 * @author shadow
 * 
 */
public class SimpleCasRealm extends CasRealm {

	@Autowired
	private CacheManager cacheManager;

	private final static Logger log = LoggerFactory.getLogger(SimpleCasRealm.class);

	public SimpleCasRealm() {
		super();
		setCacheManager(cacheManager);
	}

	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		CasToken casToken = (CasToken) token;
		if (token == null)
			return null;
		String ticket = (String) casToken.getCredentials();
		if (!StringUtils.hasText(ticket))
			return null;
		TicketValidator ticketValidator = ensureTicketValidator();
		try {
			Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
			AttributePrincipal casPrincipal = casAssertion.getPrincipal();
			String userId = casPrincipal.getName();
			log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] { ticket,
					getCasServerUrlPrefix(), userId });
			Map attributes = casPrincipal.getAttributes();
			casToken.setUserId(userId);
			String rememberMeAttributeName = getRememberMeAttributeName();
			String rememberMeStringValue = (String) attributes.get(rememberMeAttributeName);
			boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
			if (isRemembered)
				casToken.setRememberMe(true);
			List principals = CollectionUtils.asList(new Object[] { userId, attributes });
			PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());

                        // 這裡可以拿到Cas的登入賬號資訊,載入到對應許可權體系資訊放到快取中...
 
                        return new SimpleAuthenticationInfo(principalCollection, ticket);
		} catch (TicketValidationException e) {
			throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [")
					.append(ticket).append("]").toString(), e);
		}
	}

	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
		List listPrincipals = principalCollection.asList();
		Map attributes = (Map) listPrincipals.get(1);
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
                
                // 這裡可以載入快取的中的資料到認證實體...

                addRoles(simpleAuthorizationInfo, split(getDefaultRoles()));

		return simpleAuthorizationInfo;
	}

	protected List split(String s) {
		List list = new ArrayList();
		String elements[] = StringUtils.split(s, ',');
		if (elements != null && elements.length > 0) {
			String arr$[] = elements;
			int len$ = arr$.length;
			for (int i$ = 0; i$ < len$; i$++) {
				String element = arr$[i$];
				if (StringUtils.hasText(element))
					list.add(element.trim());
			}

		}
		return list;
	}

	protected void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles) {
		String role;
		for (Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))
			role = (String) i$.next();

	}

	protected void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions) {
		String permission;
		for (Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo
				.addStringPermission(permission))
			permission = (String) i$.next();

	}

	/** 重寫退出時快取處理方法 */
	protected void doClearCache(PrincipalCollection principals) {
		Object principal = principals.getPrimaryPrincipal();
		try {
			getCache().remove(principal);
			log.debug(new StringBuffer().append(principal).append(" on logout to remove the cache [").append(principal)
					.append("]").toString());
		} catch (CacheException e) {
			log.error(e.getMessage());
		}
	}

	/** 獲取快取管理器的快取堆例項 */
	protected Cache<Object, Object> getCache() throws CacheException {
		return cacheManager.getCache(CacheEmnu.MEMCACHED_DATA_CACHE);
	}

	public CacheManager getCacheManager() {
		return cacheManager;
	}

	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}

}

值得提醒大家的一個關鍵點,如何獲取cas返回過來的物件資訊呢?

Subject subject = SecurityUtils.getSubject();
		Object principal = subject.getPrincipal();
		PrincipalCollection principals = subject.getPrincipals();

第一個物件是可以獲取到當前登入賬號


第二個物件是一個List集合其中0元素是當前登入賬號,1元素是一個map集合,這裡就存放了我們cas服務給我返回的使用者資訊

我們寫的攔截器判斷是否有登入就用第一個Object判斷是否有null即可

第五步既然有登入了,那就必須有退出功能,那如何才能完整退出呢?流程應該是先執行當前系統的登出,然後再執行cas的logout,這樣就比較完整了,不會出現莫名其妙的問題

呼叫當前的shiro的subject.logout();登出當前系統的物件,然後返回到頁面

@RequestMapping("/mh/cas/logout.do")
	public ModelAndView casLogout(HttpServletRequest request, HttpServletResponse response, UserDetailsVo vo) {
		SimpleUtils.getSubject().logout();
		return createModelAndView("/mh/logout");
	}

頁面再重定向到cas的logout,這樣就把cas的ticket也登出成功

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>正在登出...</title>
<script type="text/javascript" src="${staticHost}/static/plugin/jquery/core.js"></script>
<script type="text/javascript">
	location.href="https://cas.test.com:8443/logout?service=http://test.com/mh/index.do";
</script>
</head>
<body>
</body>
</html>

我想改造大概很明白了,其實shiro-cas.jar已經大部分攔截處理已經幫我們做好了,所以我們很安心地按照以往的方式來操控shiro的登入方式,希望對還沒爬過這個坑的同學有幫助