shiro框架的使用及扩展

来源:互联网 发布:方可进销存软件下载 编辑:程序博客网 时间:2024/05/22 04:41

管理后台的权限管理模块搭建中使用了shiro框架。

       shiro的优点是:相对Spring Security较为轻巧,使用起来自由度大,和Spring框架结合的方式也很成熟。缺点是:shiro本身没实现缓存,需要自己定义缓存实现,更新比较慢,有的功能需要自己拓展。

        shiro文档:http://shiro.apache.org/static/1.2.3/apidocs/  

        十分钟入门:http://shiro.apache.org/10-minute-tutorial.html

        以下总结在项目中使用shiro的方法和管理后台项目中对shiro的拓展。

 

一、使用shiro管理权限

        1. 引入shiro需要的包。使用maven的项目中,在pom.xml增加以下依赖:

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1.              <!-- shiro权限管理 -->  
  2. <dependency>  
  3.     <groupId>org.apache.shiro</groupId>  
  4.     <artifactId>shiro-core</artifactId>  
  5.     <version>1.1.0</version>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>org.apache.shiro</groupId>  
  9.     <artifactId>shiro-web</artifactId>  
  10.     <version>1.1.0</version>  
  11. </dependency>  
  12. <dependency>  
  13.     <groupId>org.apache.shiro</groupId>  
  14.     <artifactId>shiro-spring</artifactId>  
  15.     <version>1.1.0</version>  
  16. </dependency>  
  17. <!-- shiro权限管理 end -->  

       2. 在项目中增加shiro配置。

         在spring配置文件目录下新建spring-shiro.xml。内容如下:

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <!-- Shiro权限管理配置 -->  
  2. <bean id="shiroFilter"  
  3.     class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- shiro通过一个filter控制权限-->  
  4.      <property name="securityManager" ref="securityManager" />  
  5.         <property name="loginUrl" value="/login/execute.do" />  <!-- 登陆页 -->  
  6.      <property name="successUrl" value="/index.jsp" />  <!-- 登陆成功之后跳转的页面 -->  
  7.         <property name="unauthorizedUrl" value="/login/execute.do" /> <!-- 用户在请求无权限的资源时,跳转到这个url -->  
  8.         <property name="filters">  
  9.         <util:map>  
  10.              <entry key="authc">  
  11.                   <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter" />  
  12.                 </entry>  
  13.           </util:map>  
  14.      </property>  
  15.      <property name="filterChainDefinitions"> <!-- 配置访问url资源需要用户拥有什么权限 配置的优先级由上至下-->  
  16.          <value>  
  17.             /=anon  
  18.             /template/main.jsp=user  
  19.             <!-- api用户信息 -->  
  20.                 /api/createApiUser**=perms[api:user:create]  <!-- 权限可以用":"分级,如拥有api权限相当于拥有api*权限(父权限涵盖子权限) -->  
  21.             /api/updateApiUser**=perms[api:user:update]  
  22.                 /api/*User*=perms[api:user:view]  
  23.                 /template/apiUserManage/**=perms[api:user:view]   
  24.                 <!-- api接口管理 -->  
  25.                 /api/*Interface*=perms[api:user:interface]  
  26.                 <!-- api统计数据 -->  
  27.                 /api/querySummaryData**=perms[api:data]  
  28.                 /template/apiSumData/**=perms[api:data]  
  29.               
  30.                          /api/**=perms[api:*]  
  31.                                ...  
  32.               
  33.              /**=anon  
  34.          </value>  
  35.      </property>  
  36.  </bean>  
  37.  <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  38.     <property name="realm" ref="permissionsRealm" />  <!-- 自定义登陆及获取权限的源 -->  
  39.  </bean>  
  40.   
  41.     <!-- shiro权限管理配置 end -->  
       在web.xml中增加shiro filter的配置:

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1.     <context-param>  
  2.         <param-name>contextConfigLocation</param-name>  
  3.         <param-value>  
  4.             classpath*:applicationContext.xml,  
  5.             classpath*:spring-*.xml //此处引入了spring-shiro  
  6.         </param-value>  
  7.     </context-param>  
  8.     ...  
  9.     <filter>  
  10.         <filter-name>shiroFilter</filter-name>  
  11.         <filter-class>  
  12.             org.springframework.web.filter.DelegatingFilterProxy  
  13.         </filter-class>  
  14.     </filter>  
  15.        
  16.     <filter-mapping>  
  17.         <filter-name>shiroFilter</filter-name>  
  18.         <url-pattern>/*</url-pattern>  
  19.     </filter-mapping>  

     3.  定义登陆及获取权限的源。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 认证实现类 
  3.  *  
  4.  * @author kexm 
  5.  *  
  6.  */  
  7. @Service("permissionsRealm")  
  8. public class PermissionsRealm extends AuthorizingRealm {  
  9.     @Autowired  
  10.     private AccountDao accountDao;  
  11.   
  12.     @Autowired  
  13.     private GroupDao groupDao;  
  14.   
  15.     private Account acc;  
  16.   
  17.     private static LogUtil log = LogUtil.getLogger(PermissionsRealm.class);  
  18.   
  19.     /** 
  20.      * 用户权限源(shiro调用此方法获取用户权限,至于从何处获取权限项,由我们定义。) 
  21.      */  
  22.     @Override  
  23.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  24.         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
  25.         log.info("method[doGetAuthorizationInfo] begin.");  
  26.         if (acc != null) {  
  27.             if(acc.getAdminType() == 2){//超级管理员 始终拥有所有权限  
  28.                 info.addStringPermission("*");  
  29.                 return info;  
  30.             }  
  31.             try {  
  32.                 List<UserGroup> gList = accountDao.getUserGroups(acc.getLoginName());  
  33.                 for (UserGroup g: gList) { //获取用户的组  
  34.                     log.info("method[doGetAuthorizationInfo] group<" + g.getName() + ">");  
  35.                     List<Permission> pList = groupDao.getGroupPerms(g.getId());  
  36.                     for (Permission p: pList) { //获取组内权限  
  37.                         log.info("method[doGetAuthorizationInfo] perm<" + p.getName() + "," + p.getPermList() + ">");  
  38.                         String permList = p.getPermList();  
  39.                         if (permList != null && !"".equals(permList)) {  
  40.                             String[] perms = p.getPermList().split(",");  
  41.                             for (String perm: perms) {//分别放入容器   (权限以字符串形式呈现,如"api:data"等,和spring-shiro.xml中的配置相对应)  
  42.                                 log.info("method[doGetAuthorizationInfo] add perm<" + perm + ">");  
  43.                                 info.addStringPermission(perm);  
  44.                             }  
  45.                         }  
  46.                     }  
  47.                 }  
  48.                 return info;//将用户权限返回给shiro  
  49.             } catch (Exception e) {  
  50.                 log.error("method[doGetAuthorizationInfo] e.message<" + e.getMessage() + "> e<" + e + ">", e);  
  51.             }  
  52.         }  
  53.         return null;  
  54.     }  
  55.   
  56.     /** 
  57.      * 用户登录验证源(shiro调用此方法执行认证) 
  58.      */  
  59.     @Override  
  60.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authtoken) throws AuthenticationException {  
  61.         log.info("method[doGetAuthenticationInfo] begin.");  
  62.         UsernamePasswordToken token = (UsernamePasswordToken) authtoken;  
  63.         SimpleAuthenticationInfo authenticationInfo = null;  
  64.         String userName = token.getUsername();  
  65.         String password = new String(token.getPassword());  
  66.         Login conf = DefaultConfigure.config.getLogin();  
  67.         String MD5pwd = MD5Util.generateSignature(conf.getSalt(), password);  
  68.         try {  
  69.             if (userName != null && !"".equals(userName)) {  
  70.                 acc = accountDao.login(userName, MD5pwd);  
  71.             }  
  72.             if (acc != null) {  
  73.                 doGetAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());  
  74.                 authenticationInfo = new SimpleAuthenticationInfo(token.getUsername(), token.getPassword(), getName());  
  75.                 return authenticationInfo;  
  76.             }  
  77.         } catch (Exception e) {  
  78.             log.error("method[doGetAuthenticationInfo] acc<" + acc + "> message<" + e.getMessage() + "> e<" + e + ">",  
  79.                 e);  
  80.         }  
  81.         return null;  
  82.     }  
  83.   
  84. }  

      3.  shiro中,使用subject管理用户。可以把subject理解为shiro存储用户信息的容器和操纵用户的工具。有了前几步的配置,便可以使用以下代码登入登出,并享受shiro的url权限控制了。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //登入  
  2. UsernamePasswordToken token = new UsernamePasswordToken(loginName, password);  
  3. Subject user = SecurityUtils.getSubject();  
  4. user.login(token);  
  5. //使用shiro自带的session存储用户信息 独立于httpSession  
  6. Session ss = user.getSession().setAttribute("userInfo", acc);  
  7. //登出  
  8. SecurityUtils.getSubject().logout();  

      4. 在页面中使用shiro标签。假如我们要让有权限的用户看到某些菜单或按钮,可以用以下方式。

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  
  2. <shiro:hasPermission name="api:data">  
  3.         who has permission can see  
  4. </shiro:hasPermission>  

          

      以上只使用了shiro的permission管理,shiro还支持对role的管理,如有进一步抽象的需求可以使用。


二、分布式环境,让shiro与redis结合

        假如我们的web项目是分布式部署的,则需要让shiro把session和用户权限到放到集中缓存上去。Shiro本身不实现Cache,但是提供了接口,方便更换不同的底层Cache实现。

shiro提供的cache接口:


[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public interface Cache<K, V> {    
  2.     //根据Key获取缓存中的值    
  3.     public V get(K key) throws CacheException;    
  4.     //往缓存中放入key-value,返回缓存中之前的值    
  5.     public V put(K key, V value) throws CacheException;     
  6.     //移除缓存中key对应的值,返回该值    
  7.     public V remove(K key) throws CacheException;    
  8.     //清空整个缓存    
  9.     public void clear() throws CacheException;    
  10.     //返回缓存大小    
  11.     public int size();    
  12.     //获取缓存中所有的key    
  13.     public Set<K> keys();    
  14.     //获取缓存中所有的value    
  15.     public Collection<V> values();    
  16. }    

    观察接口可以发现,我们需要实现一个keys方法。这个方法限制了shiro不能使用缓存集群(SharedRedis不提供这个方法,只有单台redis可用keys方法,望找到解决方案)。


    我们的项目使用redis作为集中缓存,shiro和redis结合的方式可以使用一个现成的工具——shiro-redis。

    shiro-redis的github:https://github.com/alexxiyang/shiro-redis

    目前管理后台项目正在使用此工具。这个工具有一处问题:读取缓存时没对读取的对象延长有效期,修复这个BUG之后还挺好用。


三、对shiro页面标签拓展,增加and or not 逻辑符

    参考:http://jinnianshilongnian.iteye.com/blog/1864800

     使用过shiro的朋友应该都知道在要想实现any permission的验证是比较麻烦。

     很多朋友刚开始接触时以为如<shiro:hasPermission name="showcase:tree:*"> 代表验证用户是否拥有tree下的任何权限,但这是错误的。如果我们把showcase:tree:*授权给用户,那么此时表示用户具有showcase:tree资源的任意权限,如<shiro:hasPermission name="showcase:tree:*">或shiro:hasPermission name="showcase:tree:create">都能验证成功。

     还有朋友认为<shiro:hasPermission name="showcase:tree:create,update"> 是或的关系,也不是,默认是且的关系。

     下载了最新的shiro1.3.0-SNAPSHOT 发现并没有增加新的标签或其他支持。

    因此我们需要简单的扩展下shiro来支持像spring security 3那样的@Secured支持表达式的强大注解。
我们扩展AuthorizingRealm,并修改:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. private static final String OR_OPERATOR = " or ";  
  2. private static final String AND_OPERATOR = " and ";  
  3. private static final String NOT_OPERATOR = "not ";  
  4. /** 
  5.  * 支持or and not 关键词  不支持and or混用 
  6.  * @param principals 
  7.  * @param permission 
  8.  * @return 
  9.  */  
  10. public boolean isPermitted(PrincipalCollection principals, String permission) {  
  11.     if(permission.contains(OR_OPERATOR)) {  
  12.         String[] permissions = permission.split(OR_OPERATOR);  
  13.         for(String orPermission : permissions) {  
  14.             if(isPermittedWithNotOperator(principals, orPermission)) {  
  15.                 return true;  
  16.             }  
  17.         }  
  18.         return false;  
  19.     } else if(permission.contains(AND_OPERATOR)) {  
  20.         String[] permissions = permission.split(AND_OPERATOR);  
  21.         for(String orPermission : permissions) {  
  22.             if(!isPermittedWithNotOperator(principals, orPermission)) {  
  23.                 return false;  
  24.             }  
  25.         }  
  26.         return true;  
  27.     } else {  
  28.         return isPermittedWithNotOperator(principals, permission);  
  29.     }  
  30. }  
  31.   
  32. private boolean isPermittedWithNotOperator(PrincipalCollection principals, String permission) {  
  33.     if(permission.startsWith(NOT_OPERATOR)) {  
  34.         return !super.isPermitted(principals, permission.substring(NOT_OPERATOR.length()));  
  35.     } else {  
  36.         return super.isPermitted(principals, permission);  
  37.     }  
  38. }  

     如上代码即可以实现简单的NOT、AND、OR支持,不过缺点是不支持复杂的如AND、OR组合。

     如下标签在拓展后可以生效:

[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <shiro:hasPermission name="api:data or api:user"></shiro:hasPermission>  
  2. <pre name="code" class="html"><shiro:hasPermission name="api:data and api:user"></shiro:hasPermission>   
[html] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. <shiro:hasPermission name="not api:data"></shiro:hasPermission>  
0 0
原创粉丝点击