shiro安全框架扩展教程--整合cas框架扩展自定义CasRealm

这次我给大家讲讲如何在shiro中整合cas框架,以及扩展自定义的角色和资源体系,啰嗦话不多说了,直接上代码说明


第一步,搭建cas服务器,我也不说拉,这个大家用现有的cas服务就行了


第二步,先加入cas-client的包到我们的项目,然后再下载个shiro-cas.jar也放到项目里


第三步配置shiro中的cas设置


[html]  view plain copy
  1. <description>shiro配置description>  
  2.   
  3.       
  4.     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  5.         <property name="cacheManager" ref="shiroCacheManager" />  
  6.         <property name="sessionManager" ref="sessionManager" />  
  7.         <property name="realm" ref="casRealm" />  
  8.         <property name="subjectFactory" ref="casSubjectFactory" />  
  9.           
  10.     bean>  
  11.   
  12.       
  13.     <bean id="sessionManager"  
  14.         class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  15.         <property name="sessionValidationSchedulerEnabled" value="false" />  
  16.         <property name="sessionDAO" ref="sessionDAO" />  
  17.         <property name="globalSessionTimeout" value="600000" />  
  18.     bean>  
  19.   
  20.       
  21.     <bean id="shiroCacheManager"  
  22.         class="com.silvery.security.shiro.cache.SimpleShiroCacheManager">  
  23.         <property name="cache" ref="shiroCache" />  
  24.     bean>  
  25.   
  26.       
  27.     <bean id="shiroCache" class="com.silvery.security.shiro.cache.SimpleShiroCache">  
  28.         <property name="cacheManager" ref="simpleCacheManager" />  
  29.     bean>  
  30.   
  31.       
  32.     <bean id="sessionDAO" class="com.silvery.security.shiro.session.CacheSessionDAO" />  
  33.   
  34.       
  35.     <bean id="simpleUserRealm" class="com.silvery.security.shiro.realm.SimpleUserRealm" />  
  36.   
  37.     <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  
  38.   
  39.     <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">  
  40.           
  41.         <property name="failureUrl"  
  42.             value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />  
  43.     bean>  
  44.   
  45.     <bean id="casRealm" class="com.silvery.security.shiro.realm.SimpleCasRealm">  
  46.         <property name="defaultRoles" value="ROLE_USER" />  
  47.         <property name="casServerUrlPrefix" value="https://cas.test.com:8443" />  
  48.           
  49.         <property name="casService" value="http://test.com/mh/cas/login.do" />  
  50.     bean>  
  51.   
  52.       
  53.     <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />  
  54.   
  55.     <bean  
  56.         class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
  57.         <property name="staticMethod"  
  58.             value="org.apache.shiro.SecurityUtils.setSecurityManager" />  
  59.         <property name="arguments" ref="securityManager" />  
  60.     bean>  
  61.   
  62.       
  63.     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  64.         <property name="securityManager" ref="securityManager" />  
  65.         <property name="loginUrl"  
  66.             value="https://cas.test.com:8443/login?service=http://test.com/mh/cas/login.do" />  
  67.         <property name="filters">  
  68.             <map>  
  69.                 <entry key="cas" value-ref="casFilter" />  
  70.                 <entry key="role">  
  71.                     <bean  
  72.                         class="com.silvery.security.shiro.filter.SimpleRoleAuthorizationFilter" />  
  73.                 entry>  
  74.                 <entry key="authc">  
  75.                     <bean  
  76.                         class="com.silvery.security.shiro.filter.SimpleFormAuthenticationFilter" />  
  77.                 entry>  
  78.                 <entry key="exec">  
  79.                     <bean class="com.silvery.security.shiro.filter.SimpleExecutiveFilter" />  
  80.                 entry>  
  81.             map>  
  82.         property>  
  83.     bean>  
  84.   
  85.       
  86.     <bean id="filterChainDefinitionsService"  
  87.         class="com.silvery.security.shiro.service.ini.impl.SimpleFilterChainDefinitionsService">  
  88.         <property name="definitions">  
  89.             <value>  
  90.                 /mh/cas/login.do = cas  
  91.                 /mh/casUrl.do = role[ROLE_USER]  
  92.                 /static/** = anon  
  93.                 /** = exec  
  94.             value>  
  95.         property>  
  96.     bean>  


关于这一个步骤的配置里面注释写的比较清楚了,至于一些类是自己重写的,可以自己参考前面的文章,/mh/cas/login.do其实就是cas拦截器的指定路径,如果想登录就请求这个路径即可,如果没有登录他会跳转cas的login页面


第四步就是需要重写我们的casrealm,你可以看到上面的配置有SimpleCasRealm,这个类是我自己重写的,是为了方便分配自己本地系统的权限体系,因为shiro-cas提供的默认CasRealm功能比较有限,不能动态角色体系,下面可以看看这个原始的CasRealm源码


[html]  view plain copy
  1. public class CasRealm extends AuthorizingRealm  
  2. {  
  3.   
  4.     public CasRealm()  
  5.     {  
  6.         validationProtocol = "CAS";  
  7.         rememberMeAttributeName = "longTermAuthenticationRequestTokenUsed";  
  8.         setAuthenticationTokenClass(org/apache/shiro/cas/CasToken);  
  9.     }  
  10.   
  11.     protected void onInit()  
  12.     {  
  13.         super.onInit();  
  14.         ensureTicketValidator();  
  15.     }  
  16.   
  17.     protected TicketValidator ensureTicketValidator()  
  18.     {  
  19.         if(ticketValidator == null)  
  20.             ticketValidator = createTicketValidator();  
  21.         return ticketValidator;  
  22.     }  
  23.   
  24.     protected TicketValidator createTicketValidator()  
  25.     {  
  26.         String urlPrefix = getCasServerUrlPrefix();  
  27.         if("saml".equalsIgnoreCase(getValidationProtocol()))  
  28.             return new Saml11TicketValidator(urlPrefix);  
  29.         else  
  30.             return new Cas20ServiceTicketValidator(urlPrefix);  
  31.     }  
  32.   
  33.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  
  34.         throws AuthenticationException  
  35.     {  
  36.         CasToken casToken = (CasToken)token;  
  37.         if(token == null)  
  38.             return null;  
  39.         String ticket = (String)casToken.getCredentials();  
  40.         if(!StringUtils.hasText(ticket))  
  41.             return null;  
  42.         TicketValidator ticketValidator = ensureTicketValidator();  
  43.         try  
  44.         {  
  45.             Assertion casAssertion = ticketValidator.validate(ticket, getCasService());  
  46.             AttributePrincipal casPrincipal = casAssertion.getPrincipal();  
  47.             String userId = casPrincipal.getName();  
  48.             log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] {  
  49.                 ticket, getCasServerUrlPrefix(), userId  
  50.             });  
  51.             Map attributes = casPrincipal.getAttributes();  
  52.             casToken.setUserId(userId);  
  53.             String rememberMeAttributeName = getRememberMeAttributeName();  
  54.             String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);  
  55.             boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);  
  56.             if(isRemembered)  
  57.                 casToken.setRememberMe(true);  
  58.             List principals = CollectionUtils.asList(new Object[] {  
  59.                 userId, attributes  
  60.             });  
  61.             PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());  
  62.             return new SimpleAuthenticationInfo(principalCollection, ticket);  
  63.         }  
  64.         catch(TicketValidationException e)  
  65.         {  
  66.             throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [").append(ticket).append("]").toString(), e);  
  67.         }  
  68.     }  
  69.   
  70.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)  
  71.     {  
  72.         SimplePrincipalCollection principalCollection = (SimplePrincipalCollection)principals;  
  73.         List listPrincipals = principalCollection.asList();  
  74.         Map attributes = (Map)listPrincipals.get(1);  
  75.         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();  
  76.         addRoles(simpleAuthorizationInfo, split(defaultRoles));  
  77.         addPermissions(simpleAuthorizationInfo, split(defaultPermissions));  
  78.         List attributeNames = split(roleAttributeNames);  
  79.         String value;  
  80.         for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addRoles(simpleAuthorizationInfo, split(value)))  
  81.         {  
  82.             String attributeName = (String)i$.next();  
  83.             value = (String)attributes.get(attributeName);  
  84.         }  
  85.   
  86.         attributeNames = split(permissionAttributeNames);  
  87.         String value;  
  88.         for(Iterator i$ = attributeNames.iterator(); i$.hasNext(); addPermissions(simpleAuthorizationInfo, split(value)))  
  89.         {  
  90.             String attributeName = (String)i$.next();  
  91.             value = (String)attributes.get(attributeName);  
  92.         }  
  93.   
  94.         return simpleAuthorizationInfo;  
  95.     }  
  96.   
  97.     private List split(String s)  
  98.     {  
  99.         List list = new ArrayList();  
  100.         String elements[] = StringUtils.split(s, ',');  
  101.         if(elements != null && elements.length > 0)  
  102.         {  
  103.             String arr$[] = elements;  
  104.             int len$ = arr$.length;  
  105.             for(int i$ = 0; i$ < len$; i$++)  
  106.             {  
  107.                 String element = arr$[i$];  
  108.                 if(StringUtils.hasText(element))  
  109.                     list.add(element.trim());  
  110.             }  
  111.   
  112.         }  
  113.         return list;  
  114.     }  
  115.   
  116.     private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles)  
  117.     {  
  118.         String role;  
  119.         for(Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))  
  120.             role = (String)i$.next();  
  121.   
  122.     }  
  123.   
  124.     private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions)  
  125.     {  
  126.         String permission;  
  127.         for(Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo.addStringPermission(permission))  
  128.             permission = (String)i$.next();  
  129.   
  130.     }  
  131.   
  132.     public String getCasServerUrlPrefix()  
  133.     {  
  134.         return casServerUrlPrefix;  
  135.     }  
  136.   
  137.     public void setCasServerUrlPrefix(String casServerUrlPrefix)  
  138.     {  
  139.         this.casServerUrlPrefix = casServerUrlPrefix;  
  140.     }  
  141.   
  142.     public String getCasService()  
  143.     {  
  144.         return casService;  
  145.     }  
  146.   
  147.     public void setCasService(String casService)  
  148.     {  
  149.         this.casService = casService;  
  150.     }  
  151.   
  152.     public String getValidationProtocol()  
  153.     {  
  154.         return validationProtocol;  
  155.     }  
  156.   
  157.     public void setValidationProtocol(String validationProtocol)  
  158.     {  
  159.         this.validationProtocol = validationProtocol;  
  160.     }  
  161.   
  162.     public String getRememberMeAttributeName()  
  163.     {  
  164.         return rememberMeAttributeName;  
  165.     }  
  166.   
  167.     public void setRememberMeAttributeName(String rememberMeAttributeName)  
  168.     {  
  169.         this.rememberMeAttributeName = rememberMeAttributeName;  
  170.     }  
  171.   
  172.     public String getDefaultRoles()  
  173.     {  
  174.         return defaultRoles;  
  175.     }  
  176.   
  177.     public void setDefaultRoles(String defaultRoles)  
  178.     {  
  179.         this.defaultRoles = defaultRoles;  
  180.     }  
  181.   
  182.     public String getDefaultPermissions()  
  183.     {  
  184.         return defaultPermissions;  
  185.     }  
  186.   
  187.     public void setDefaultPermissions(String defaultPermissions)  
  188.     {  
  189.         this.defaultPermissions = defaultPermissions;  
  190.     }  
  191.   
  192.     public String getRoleAttributeNames()  
  193.     {  
  194.         return roleAttributeNames;  
  195.     }  
  196.   
  197.     public void setRoleAttributeNames(String roleAttributeNames)  
  198.     {  
  199.         this.roleAttributeNames = roleAttributeNames;  
  200.     }  
  201.   
  202.     public String getPermissionAttributeNames()  
  203.     {  
  204.         return permissionAttributeNames;  
  205.     }  
  206.   
  207.     public void setPermissionAttributeNames(String permissionAttributeNames)  
  208.     {  
  209.         this.permissionAttributeNames = permissionAttributeNames;  
  210.     }  
  211.   
  212.     public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";  
  213.     public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";  
  214.     private static Logger log = LoggerFactory.getLogger(org/apache/shiro/cas/CasRealm);  
  215.     private String casServerUrlPrefix;  
  216.     private String casService;  
  217.     private String validationProtocol;  
  218.     private String rememberMeAttributeName;  
  219.     private TicketValidator ticketValidator;  
  220.     private String defaultRoles;  
  221.     private String defaultPermissions;  
  222.     private String roleAttributeNames;  
  223.     private String permissionAttributeNames;  
  224.   
  225. }  

其实跟我们普通用的UserRealm或者是JdbcRealm差别不大,但是里面增加了casToken的验证,所以我们应该直接拿过来用,下面再加载出我们的自己的逻辑即可,所以我们可以选择继承当前的CasRealm重载一下他的两个方法


[html]  view plain copy
  1. /**  
  2.  *   
  3.  * 扩展CAS桥接器,订制角色体系和资源体系  
  4.  *   
  5.  * @author shadow  
  6.  *   
  7.  */  
  8. public class SimpleCasRealm extends CasRealm {  
  9.   
  10.     @Autowired  
  11.     private CacheManager cacheManager;  
  12.   
  13.     private final static Logger log = LoggerFactory.getLogger(SimpleCasRealm.class);  
  14.   
  15.     public SimpleCasRealm() {  
  16.         super();  
  17.         setCacheManager(cacheManager);  
  18.     }  
  19.   
  20.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
  21.         CasToken casToken = (CasToken) token;  
  22.         if (token == null)  
  23.             return null;  
  24.         String ticket = (String) casToken.getCredentials();  
  25.         if (!StringUtils.hasText(ticket))  
  26.             return null;  
  27.         TicketValidator ticketValidator = ensureTicketValidator();  
  28.         try {  
  29.             Assertion casAssertion = ticketValidator.validate(ticket, getCasService());  
  30.             AttributePrincipal casPrincipal = casAssertion.getPrincipal();  
  31.             String userId = casPrincipal.getName();  
  32.             log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[] { ticket,  
  33.                     getCasServerUrlPrefix(), userId });  
  34.             Map attributes = casPrincipal.getAttributes();  
  35.             casToken.setUserId(userId);  
  36.             String rememberMeAttributeName = getRememberMeAttributeName();  
  37.             String rememberMeStringValue = (String) attributes.get(rememberMeAttributeName);  
  38.             boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);  
  39.             if (isRemembered)  
  40.                 casToken.setRememberMe(true);  
  41.             List principals = CollectionUtils.asList(new Object[] { userId, attributes });  
  42.             PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());  
  43.   
  44.                         // 这里可以拿到Cas的登录账号信息,加载到对应权限体系信息放到缓存中...  
  45.    
  46.                         return new SimpleAuthenticationInfo(principalCollection, ticket);  
  47.         } catch (TicketValidationException e) {  
  48.             throw new CasAuthenticationException((new StringBuilder()).append("Unable to validate ticket [")  
  49.                     .append(ticket).append("]").toString(), e);  
  50.         }  
  51.     }  
  52.   
  53.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  54.         SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;  
  55.         List listPrincipals = principalCollection.asList();  
  56.         Map attributes = (Map) listPrincipals.get(1);  
  57.         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();  
  58.                   
  59.                 // 这里可以加载缓存的中的数据到认证实体...  
  60.   
  61.                 addRoles(simpleAuthorizationInfo, split(getDefaultRoles()));  
  62.   
  63.         return simpleAuthorizationInfo;  
  64.     }  
  65.   
  66.     protected List split(String s) {  
  67.         List list = new ArrayList();  
  68.         String elements[] = StringUtils.split(s, ',');  
  69.         if (elements != null && elements.length > 0) {  
  70.             String arr$[] = elements;  
  71.             int len$ = arr$.length;  
  72.             for (int i$ = 0; i$ < len$; i$++) {  
  73.                 String element = arr$[i$];  
  74.                 if (StringUtils.hasText(element))  
  75.                     list.add(element.trim());  
  76.             }  
  77.   
  78.         }  
  79.         return list;  
  80.     }  
  81.   
  82.     protected void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List roles) {  
  83.         String role;  
  84.         for (Iterator i$ = roles.iterator(); i$.hasNext(); simpleAuthorizationInfo.addRole(role))  
  85.             role = (String) i$.next();  
  86.   
  87.     }  
  88.   
  89.     protected void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List permissions) {  
  90.         String permission;  
  91.         for (Iterator i$ = permissions.iterator(); i$.hasNext(); simpleAuthorizationInfo  
  92.                 .addStringPermission(permission))  
  93.             permission = (String) i$.next();  
  94.   
  95.     }  
  96.   
  97.     /** 重写退出时缓存处理方法 */  
  98.     protected void doClearCache(PrincipalCollection principals) {  
  99.         Object principal = principals.getPrimaryPrincipal();  
  100.         try {  
  101.             getCache().remove(principal);  
  102.             log.debug(new StringBuffer().append(principal).append(" on logout to remove the cache [").append(principal)  
  103.                     .append("]").toString());  
  104.         } catch (CacheException e) {  
  105.             log.error(e.getMessage());  
  106.         }  
  107.     }  
  108.   
  109.     /** 获取缓存管理器的缓存堆实例 */  
  110.     protected Cache<Object, Object> getCache() throws CacheException {  
  111.         return cacheManager.getCache(CacheEmnu.MEMCACHED_DATA_CACHE);  
  112.     }  
  113.   
  114.     public CacheManager getCacheManager() {  
  115.         return cacheManager;  
  116.     }  
  117.   
  118.     public void setCacheManager(CacheManager cacheManager) {  
  119.         this.cacheManager = cacheManager;  
  120.     }  
  121.   
  122. }  


值得提醒大家的一个关键点,如何获取cas返回过来的对象信息呢?


[html]  view plain copy
  1. Subject subject = SecurityUtils.getSubject();  
  2.         Object principal = subject.getPrincipal();  
  3.         PrincipalCollection principals = subject.getPrincipals();  

第一个对象是可以获取到当前登录账号


第二个对象是一个List集合其中0元素是当前登录账号,1元素是一个map集合,这里就存放了我们cas服务给我返回的用户信息


我们写的拦截器判断是否有登录就用第一个Object判断是否有null即可


第五步既然有登录了,那就必须有退出功能,那如何才能完整退出呢?流程应该是先执行当前系统的注销,然后再执行cas的logout,这样就比较完整了,不会出现莫名其妙的问题


调用当前的shiro的subject.logout();注销当前系统的对象,然后返回到页面

[html]  view plain copy
  1. @RequestMapping("/mh/cas/logout.do")  
  2.     public ModelAndView casLogout(HttpServletRequest request, HttpServletResponse response, UserDetailsVo vo) {  
  3.         SimpleUtils.getSubject().logout();  
  4.         return createModelAndView("/mh/logout");  
  5.     }  

页面再重定向到cas的logout,这样就把cas的ticket也注销成功

[html]  view plain copy
  1. >  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
  5. <title>正在注销...title>  
  6. <script type="text/javascript" src="${staticHost}/static/plugin/jquery/core.js">script>  
  7. <script type="text/javascript">  
  8.     location.href="https://cas.test.com:8443/logout?service=http://test.com/mh/index.do";  
  9. script>  
  10. head>  
  11. <body>  
  12. body>  
  13. html>  


我想改造大概很明白了,其实shiro-cas.jar已经大部分拦截处理已经帮我们做好了,所以我们很安心地按照以往的方式来操控shiro的登录方式,希望对还没爬过这个坑的同学有帮助

你可能感兴趣的:(Spring,cas,shiro,cas,shiro)