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的登录方式,希望对还没爬过这个坑的同学有帮助


你可能感兴趣的:(框架,开源,shiro,安全,缓存机制)