这次我给大家讲讲如何在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" />
-
- 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>
-
-
- <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也注销成功
- >
- <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的登录方式,希望对还没爬过这个坑的同学有帮助