jfinal-shiro的shiro注解结合数据库url路径过滤,动态维护你的权限系统,支持ajax

https://github.com/Dreampie/jfinal-shiro  的jfinal-shiro插件:

<dependency>
      <groupId>cn.dreampie</groupId>
      <artifactId>jfinal-shiro</artifactId>
      <version>${jfinal-shiro.version}</version>
 </dependency>

目前刚刚发布第一个版本0.1:

<jfinal-shiro.version>0.1</jfinal-shiro.version>

首先感谢jfinal-ext中原作者,该插件主要是针对ext插件的部分改进。

下面主要介绍两种使用方式:

    在web.xml里添加

    <!--权限过滤器 start-->
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <!--权限过滤器 end-->

    启用shiro   在jfinal config里configPlugin方法添加

 
 //shiro权限框架   在jfinal  plugins里添加shiro  plugin
        plugins.add(new ShiroPlugin(routes, new MyJdbcAuthzService()));

    添加shiro的过滤器  在jfinal config里configInterceptor方法添加

interceptors.add(new ShiroInterceptor());

public class User extends cn.dreampie.shiro.model.User<User> //user的model需要继承User
  1. 在方法上使用注解

    1. Shiro共有5个注解,分别如下:

      1. RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。

      2. RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。

      3. RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。

      4. RequiresRoles:当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。

      5. RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

    2. Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):
      RequiresRoles
      RequiresPermissions
      RequiresAuthentication
      RequiresUser
      RequiresGuest
      例如:你同时生命了RequiresRoles和RequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。

    3. RequiresRoles可以用在Controller或者方法上。可以多个roles,默认逻辑为 AND也就是所有具备所有role才能访问。

            示例:

    //属于user角色
    @RequiresRoles("user")
     
    //必须同时属于user和admin角色
    @RequiresRoles({"user","admin"})
     
    //属于user或者admin之一。
    @RequiresRoles(value={"user","admin"},logical=Logical.OR)



  2. 其他使用方法类似。

  3. @RequiresPermissions
    @RequiresAuthentication
    @RequiresUser
    @RequiresGusst



  4. 详细可以参考玛雅牛的shiro注解使用,http://my.oschina.net/myaniu/blog/137205

  5. 基于数据库的权限设计与维护

    数据库的基本权限结构主要:用户->角色->权限

    表结构设计如下(h2数据库,使用其他数据修改部分sql语句之后使用):

    DROP TABLE IF EXISTS sec_user;
    DROP SEQUENCE IF EXISTS sec_user_id_seq;
    CREATE SEQUENCE sec_user_id_seq START WITH 1;
    CREATE TABLE sec_user (  --用户表
      id            BIGINT       NOT NULL DEFAULT NEXTVAL('sec_user_id_seq') PRIMARY KEY,
      username      VARCHAR(50)  NOT NULL COMMENT '登录名',
      providername  VARCHAR(50)  NOT NULL COMMENT '提供者',
      email         VARCHAR(200) COMMENT '邮箱',
      mobile        VARCHAR(50) COMMENT '手机',
      password      VARCHAR(200) NOT NULL COMMENT '密码',
      hasher   VARCHAR(200) NOT NULL COMMENT '加密类型',
      salt VARCHAR(200) NOT NULL COMMENT '加密盐',
      avatar_url    VARCHAR(255) COMMENT '头像',
      first_name    VARCHAR(10) COMMENT '名字',
      last_name     VARCHAR(10) COMMENT '姓氏',
      full_name     VARCHAR(20) COMMENT '全名',
      department_id BIGINT NOT NULL COMMENT '部门id',
      created_at    TIMESTAMP    NOT NULL,
      updated_at    TIMESTAMP,
      deleted_at    TIMESTAMP
    );
    
    DROP TABLE IF EXISTS sec_user_info;
    DROP SEQUENCE IF EXISTS sec_user_info_id_seq;
    CREATE SEQUENCE sec_user_info_id_seq START WITH 1;
    CREATE TABLE sec_user_info (-- 用户详细信息表
      id          BIGINT    NOT NULL DEFAULT NEXTVAL('sec_user_info_id_seq') PRIMARY KEY,
      user_id     BIGINT    NOT NULL COMMENT '用户id',
      creator_id  BIGINT COMMENT '创建者id',
      gender      INT DEFAULT 0 COMMENT '性别0男,1女',
      province_id BIGINT COMMENT '省id',
      city_id     BIGINT COMMENT '市id',
      county_id   BIGINT COMMENT '县id',
      street      VARCHAR(500) COMMENT '街道',
      zip_code    VARCHAR(50) COMMENT '邮编',
      created_at  TIMESTAMP NOT NULL,
      updated_at  TIMESTAMP,
      deleted_at  TIMESTAMP
    );
    
    DROP TABLE IF EXISTS sec_role;
    DROP SEQUENCE IF EXISTS sec_role_id_seq;
    CREATE SEQUENCE sec_role_id_seq START WITH 1;
    CREATE TABLE sec_role (--角色表
      id         BIGINT      NOT NULL DEFAULT NEXTVAL('sec_role_id_seq') PRIMARY KEY,
      name       VARCHAR(50) NOT NULL COMMENT '名称',
      value      VARCHAR(50) NOT NULL COMMENT '值',
      intro      VARCHAR(255) COMMENT '简介',
      pid        BIGINT DEFAULT 0 COMMENT '父级id',
      left_code  BIGINT DEFAULT 0 COMMENT '数据左边码',
      right_code BIGINT DEFAULT 0 COMMENT '数据右边码',
      created_at TIMESTAMP   NOT NULL,
      updated_at TIMESTAMP,
      deleted_at TIMESTAMP
    );
    
    DROP TABLE IF EXISTS sec_user_role;
    DROP SEQUENCE IF EXISTS sec_user_role_id_seq;
    CREATE SEQUENCE sec_user_role_id_seq START WITH 1;
    CREATE TABLE sec_user_role (--用户角色关系表
      id      BIGINT NOT NULL DEFAULT NEXTVAL('sec_user_role_id_seq') PRIMARY KEY,
      user_id BIGINT NOT NULL,
      role_id BIGINT NOT NULL
    );
    
    DROP TABLE IF EXISTS sec_permission;
    DROP SEQUENCE IF EXISTS sec_permission_id_seq;
    CREATE SEQUENCE sec_permission_id_seq START WITH 1;
    CREATE TABLE sec_permission (--权限表
      id         BIGINT      NOT NULL DEFAULT NEXTVAL('sec_permission_id_seq') PRIMARY KEY,
      name       VARCHAR(50) NOT NULL COMMENT '名称',
      value      VARCHAR(50) NOT NULL COMMENT '值',
      url        VARCHAR(255) COMMENT 'url地址',
      intro      VARCHAR(255) COMMENT '简介',
      pid        BIGINT DEFAULT 0 COMMENT '父级id',
      left_code  BIGINT DEFAULT 0 COMMENT '数据左边码',
      right_code BIGINT DEFAULT 0 COMMENT '数据右边码',
      created_at TIMESTAMP   NOT NULL,
      updated_at TIMESTAMP,
      deleted_at TIMESTAMP
    );
    
    
    DROP TABLE IF EXISTS sec_role_permission;
    DROP SEQUENCE IF EXISTS sec_role_permission_id_seq;
    CREATE SEQUENCE sec_role_permission_id_seq START WITH 1;
    CREATE TABLE sec_role_permission (--角色权限关系表
      id            BIGINT NOT NULL DEFAULT NEXTVAL('sec_role_permission_id_seq') PRIMARY KEY,
      role_id       BIGINT NOT NULL,
      permission_id BIGINT NOT NULL
    );

            数据结构基本完成,提示:pid,left_code,right_code是数据的树形结构设计和权限无关

            测试数据:

    --create role--
    
    INSERT INTO sec_role(id,name, value, intro, pid,left_code,right_code,created_at)
    VALUES (sec_role_id_seq.nextval,'超级管理员','R_ADMIN','',0,1,8, current_timestamp),
           (sec_role_id_seq.nextval,'系统管理员','R_MANAGER','',1,2,7,current_timestamp),
           (sec_role_id_seq.nextval,'会员','R_MEMBER','',2,3,4,current_timestamp),
           (sec_role_id_seq.nextval,'普通用户','R_USER','',2,5,6,current_timestamp);
    
    --create permission--
    INSERT INTO sec_permission(id, name, value, url, intro,pid,left_code,right_code, created_at)
    VALUES (sec_permission_id_seq.nextval,'管理员目录','P_D_ADMIN','/admin/**','',0,1,6,current_timestamp),
           (sec_permission_id_seq.nextval,'角色权限管理','P_ROLE','/admin/role/**','',1,2,3,current_timestamp),
           (sec_permission_id_seq.nextval,'用户管理','P_USER','/admin/user/**','',1,4,5,current_timestamp),
           (sec_permission_id_seq.nextval,'会员目录','P_D_MEMBER','/member/**','',0,9,10,current_timestamp),
           (sec_permission_id_seq.nextval,'普通用户目录','P_D_USER','/user/**','',0,11,12,current_timestamp);
    
    
    INSERT INTO sec_role_permission(id,role_id, permission_id)
    VALUES (sec_role_permission_id_seq.nextval,1,1),(sec_role_permission_id_seq.nextval,1,2),
           (sec_role_permission_id_seq.nextval,1,3),(sec_role_permission_id_seq.nextval,1,4),
           (sec_role_permission_id_seq.nextval,1,5),
    
           (sec_role_permission_id_seq.nextval,2,1),(sec_role_permission_id_seq.nextval,2,3),
           (sec_role_permission_id_seq.nextval,2,4),(sec_role_permission_id_seq.nextval,2,5),
    
           (sec_role_permission_id_seq.nextval,3,4),(sec_role_permission_id_seq.nextval,3,5),
    
           (sec_role_permission_id_seq.nextval,4,5);
      --user data--
    --create  admin--
    INSERT INTO sec_user(id, username, providername, email, mobile, password, hasher, salt, avatar_url, first_name, last_name, full_name,department_id, created_at)
    VALUES (sec_user_id_seq.nextval,'admin','dreampie','[email protected]','18611434500','$shiro1$SHA-256$500000$ZMhNGAcL3HbpTbNXzxxT1Q==$wRi5AF6BK/1FsQdvISIY1lJ9Rm/aekBoChjunVsqkUU=','default_hasher','','','仁辉','王','仁辉&middot;王',1,current_timestamp),
           (sec_user_id_seq.nextval,'aaaaa','dreampie','[email protected]','18511400000','$shiro1$SHA-256$500000$ZMhNGAcL3HbpTbNXzxxT1Q==$wRi5AF6BK/1FsQdvISIY1lJ9Rm/aekBoChjunVsqkUU=','default_hasher','','','金彤','刘','金彤&middot;刘',2,current_timestamp);
    
    --create user_info--
    INSERT INTO sec_user_info(id, user_id, creator_id, gender,province_id,city_id,county_id,street,created_at)
    VALUES (sec_user_info_id_seq.nextval,1,0,0,1,2,3,'人民大学',current_timestamp),
           (sec_user_info_id_seq.nextval,2,0,0,1,2,3,'人民大学',current_timestamp);
    
    --create user_role--
    INSERT INTO sec_user_role(id, user_id, role_id)
    VALUES (sec_user_role_id_seq.nextval,1,1),
           (sec_user_role_id_seq.nextval,2,2);

            接下来实现两个关键接口,一个是shiro的JdbcRealm:        

    public class MyJdbcRealm extends AuthorizingRealm {
    
      /**
       * 登录认证
       *
       * @param token
       * @return
       * @throws org.apache.shiro.authc.AuthenticationException
       */
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        User user = null;
        String username = userToken.getUsername();
        if (ValidateKit.isEmail(username)) {
          user = User.dao.findFirstBy(" `user`.email =? AND `user`.deleted_at is null", username);
        } else if (ValidateKit.isMobile(username)) {
          user = User.dao.findFirstBy(" `user`.mobile =? AND `user`.deleted_at is null", username);
        } else {
          user = User.dao.findFirstBy(" `user`.username =? AND `user`.deleted_at is null", username);
        }
        if (user != null) {
          SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getStr("password"), getName());
          return info;
        } else {
          return null;
        }
      }
    
      /**
       * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
       *
       * @param principals
       * @return
       */
      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String loginName = ((User) principals.fromRealm(getName()).iterator().next()).get("username");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> roleSet = new LinkedHashSet<String>(); // 角色集合
        Set<String> permissionSet = new LinkedHashSet<String>();  // 权限集合
        List<Role> roles = null;
        User user = User.dao.findFirstBy(" `user`.username =? AND `user`.deleted_at is null", loginName);
        if (user != null) {
          //遍历角色
          roles = Role.dao.findUserBy("", user.getLong("id"));
        } else {
          SubjectKit.getSubject().logout();
        }
    
        loadRole(roleSet, permissionSet, roles);
        info.setRoles(roleSet); // 设置角色
        info.setStringPermissions(permissionSet); // 设置权限
        return info;
      }
    
      /**
       * @param roleSet
       * @param permissionSet
       * @param roles
       */
      private void loadRole(Set<String> roleSet, Set<String> permissionSet, List<Role> roles) {
        List<Permission> permissions;
        for (Role role : roles) {
          //角色可用
          if (role.getDate("deleted_at") == null) {
            roleSet.add(role.getStr("value"));
            permissions = Permission.dao.findByRole("", role.getLong("id"));
            loadAuth(permissionSet, permissions);
          }
        }
      }
    
      /**
       * @param permissionSet
       * @param permissions
       */
      private void loadAuth(Set<String> permissionSet, List<Permission> permissions) {
        //遍历权限
        for (Permission permission : permissions) {
          //权限可用
          if (permission.getDate("deleted_at") == null) {
            permissionSet.add(permission.getStr("value"));
          }
        }
      }
    
      /**
       * 更新用户授权信息缓存.
       */
    
      public void clearCachedAuthorizationInfo(Object principal) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
        clearCachedAuthorizationInfo(principals);
      }
    
      /**
       * 清除所有用户授权信息缓存.
       */
      public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
          for (Object key : cache.keys()) {
            cache.remove(key);
          }
        }
      }
    }

         实现数据库权限的初始化加载:

    public class MyJdbcAuthzService implements JdbcAuthzService {
      @Override
      public Map<String, AuthzHandler> getJdbcAuthz() {
        //加载数据库的url配置
        Map<String, AuthzHandler> authzJdbcMaps = new HashMap<String, AuthzHandler>();
        //遍历角色
        List<Role> roles = Role.dao.findAll();
        List<Permission> permissions = null;
        for (Role role : roles) {
          //角色可用
          if (role.getDate("daleted_at") == null) {
            permissions = Permission.dao.findByRole("", role.get("id"));
            //遍历权限
            for (Permission permission : permissions) {
              //权限可用
              if (permission.getDate("daleted_at") == null) {
                if (permission.getStr("url") != null && !permission.getStr("url").isEmpty()) {
                  authzJdbcMaps.put(permission.getStr("url"), new JdbcPermissionAuthzHandler(permission.getStr("value")));
                }
              }
            }
          }
        }
        return authzJdbcMaps;
      }
    }

前台使用验证码时传入username,password,captcha  三个参数,第三个是验证码参数名,提前把验证码内容存入session,shiro会自动进行验证,注意名称为captcha

主要结构是权限表里的url-value,如果需要访问

url:  /admin/**   需要value:P_D_ADMIN

把这些权限绑定到角色之后,角色绑定给用户就相当于,用户下面有很多这些  url-value

    1.系统启动的时候把这个对应关系加载到内存或者缓存  //cn.dreampie.shiro.core.ShiroKit

    2. 用户登录的时候把用户对应的角色所有的权限加载到缓存,这一步是shiro自己实现

    3.当用户访问某个url的时候 如访问/admin/index,过滤器会匹配到/admin/**,这个url需要拥有P_D_ADMIN的权限

    4.然后使用shiro的接口hasPremission(value),判断用户是否拥有这个权限//cn.dreampie.shiro.core.ShiroInterceptor

    5.放行或者拒绝访问返回403状态

jfinal-shiro支持Ajax登陆/退出,使用json数据

shiro.ini  配置文件:

[users]
guest = guest,guest

[main]
authc = cn.dreampie.shiro.ShiroFormAuthenticationFilter
#登陆请求路径
authc.loginUrl = /signin
#分角色登录配置
#authc.loginUrlMap = user:/login,admin:/admin/login
#登陆成功跳转路径
authc.successUrl = /
#登陆失败跳转路径
authc.failureUrl = /signin
#登陆成功跳转路径
#authc.failureUrlMap = user:/login.ftl,admin:/admin/login.ftl

signout = cn.dreampie.shiro.ShiroLogoutFilter
#退出跳转路径
signout.redirectUrl = /
#分角色退出跳转
#logout.redirectUrlMap = user:/index,admin:/index
#realm  上面实现的获取登陆用户和用户权限的借口类
jdbcRealm = org.icedog.common.shiro.MyJdbcRealm
securityManager.realm = $jdbcRealm

passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordMatcher = cn.dreampie.shiro.ShiroPasswordMatcher
passwordMatcher.passwordService = $passwordService
jdbcRealm.credentialsMatcher = $passwordMatcher

#cache
shiroCacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
shiroCacheManager.cacheManagerConfigFile = classpath:ehcache.xml
securityManager.cacheManager = $shiroCacheManager

#session
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionDAO.activeSessionsCacheName = shiro-activeSessionCache
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager
sessionListener = cn.dreampie.shiro.listeners.ShiroSessionListener
securityManager.sessionManager.sessionListeners = $sessionListener

# cookie for single sign on
#cookie = org.apache.shiro.web.servlet.SimpleCookie
#cookie.name = www.dreampie.cn.session
#cookie.path = /
#cookie.maxAge = -1
#sessionManager.sessionIdCookie = $cookie
# 1,800,000 milliseconds = 30 mins
securityManager.sessionManager.globalSessionTimeout = 1200000
;sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
;sessionValidationScheduler.interval = 1200000
;securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
securityManager.sessionManager.deleteInvalidSessions = false
;securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false

[urls]
/signin = authc
/signout = signout
/** = anon



如果你使用freemarker作为模板,推荐使用jfinal-shiro-freemarker标签库 http://my.oschina.net/wangrenhui1990/blog/312741

https://github.com/Dreampie?tab=repositories 目录下有多款插件:

cn.dreampie.jfinal-shiro     https://github.com/Dreampie/jfinal-shiro    shiro插件

cn.dreampie.jfinal-shiro-freemarker   https://github.com/Dreampie/jfinal-shiro-freemarker    shiro插件实现的freemarker标签库

cn.dreampie.jfinal-web     https://github.com/Dreampie/jfinal-web   相关web插件,简洁model实现

cn.dreampie.jfinal-utils        https://github.com/Dreampie/jfinal-utils   部分jfinal工具

cn.dreampie.jfinal-tablebind        https://github.com/Dreampie/jfinal-tablebind   jfinal的table自动绑定插件,支持多数据源

cn.dreampie.jfinal-flyway      https://github.com/Dreampie/jfinal-flyway   数据库脚本升级插件,开发中升级应用时,使用脚本同步升级数据库或者回滚

cn.dreampie.jfinal-captcha      https://github.com/Dreampie/jfinal-captcha   基于jfinal render的超简单验证吗插件

cn.dreampie.jfinal-quartz       https://github.com/Dreampie/jfinal-quartz   基于jfinal 的quartz管理器

cn.dreampie.jfinal-sqlinxml      https://github.com/Dreampie/jfinal-sqlinxml   基于jfinal 的类似ibatis的sql语句管理方案

cn.dreampie.jfinal-lesscss       https://github.com/Dreampie/jfinal-lesscss   java实现的lesscsss实时编译插件,可以由于jfinal

cn.dreampie.jfinal-coffeescript     https://github.com/Dreampie/jfinal-coffeescript   java实现的coffeescript实时编译插件,可以由于jfinal 

cn.dreampie.jfinal-akka    https://github.com/Dreampie/jfinal-akka   java使用akka执行异步任务

cn.dreampie.jfinal-mailer       https://github.com/Dreampie/jfinal-mailer   使用akka发布邮件的jfinal插件

cn.dreampie.jfinal-slf4j     https://github.com/Dreampie/jfinal-slf4j   让jfinal使用slf4j的日志api

部分内容借鉴了网络资料

你可能感兴趣的:(java,shiro,jFinal)