Skip to content

Dreampie/jfinal-shiro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 

Repository files navigation

jfinal-shiro

jfinal shiro plugin 最简单,最灵活的权限框架实现,查看其他插件-> Maven

maven 引用 ${jfinal-shiro.version}替换为相应的版本如:0.2

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

支持不同角色登陆到不同的url,支持ajax登陆/登出,例如:icedog

推荐的数据库权限表结构设计

DROP TABLE IF EXISTS sec_user;
CREATE TABLE sec_user (
  id            BIGINT       NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username      VARCHAR(50)  NOT NULL COMMENT '登录名',
  providername  VARCHAR(50)  NOT NULL COMMENT '提供者',
  email         VARCHAR(200) COMMENT '邮箱',
  phone        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 '全名',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP   NOT NULL,
  updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
  deleted_at TIMESTAMP NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='用户';

DROP TABLE IF EXISTS sec_user_info;
CREATE TABLE sec_user_info (
  id          BIGINT    NOT NULL AUTO_INCREMENT 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 DEFAULT CURRENT_TIMESTAMP   NOT NULL,
  updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
  deleted_at TIMESTAMP NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='用户信息';

DROP TABLE IF EXISTS sec_role;
CREATE TABLE sec_role (
  id         BIGINT    NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(50)   NOT NULL COMMENT '名称',
  value      VARCHAR(50)  NOT NULL COMMENT '',
  intro      VARCHAR(255) COMMENT '简介',
  pid        BIGINT DEFAULT 0 COMMENT '父级id',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP   NOT NULL,
  updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
  deleted_at TIMESTAMP NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='角色';

DROP TABLE IF EXISTS sec_user_role;
CREATE TABLE sec_user_role (
  id      BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  role_id BIGINT NOT NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='用户角色';

DROP TABLE IF EXISTS sec_permission;
CREATE TABLE sec_permission (
  id         BIGINT      NOT NULL AUTO_INCREMENT 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',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP   NOT NULL,
  updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
  deleted_at TIMESTAMP NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='权限';


DROP TABLE IF EXISTS sec_role_permission;
CREATE TABLE sec_role_permission (
  id            BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  role_id       BIGINT NOT NULL,
  permission_id BIGINT NOT NULL
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='角色权限';


-- create role--

INSERT INTO sec_role(id,name, value, intro, pid,created_at)
VALUES (1,'超级管理员','R_ADMIN','',0, current_timestamp),
       (2,'系统管理员','R_MANAGER','',1,current_timestamp),
       (3,'总部','R_MEMBER','',2,current_timestamp),
       (4,'分部','R_USER','',2,current_timestamp);

-- create permission--
INSERT INTO sec_permission(id, name, value, url, intro,pid, created_at)
VALUES (1,'管理员目录','P_D_ADMIN','/admin/**','',0,current_timestamp),
       (2,'角色权限管理','P_ROLE','/admin/role/**','',1,current_timestamp),
       (3,'用户管理','P_USER','/admin/user/**','',1,current_timestamp),
       (4,'总部目录','P_D_MEMBER','/member/**','',0,current_timestamp),
       (5,'分部目录','P_D_USER','/user/**','',0,current_timestamp),
       (6,'用户处理','P_USER_CONTROL','/user/branch**','',5,current_timestamp),
       (7,'订单','P_ORDER','/order/**','',0,current_timestamp),
       (8,'订单处理','P_ORDER_CONTROL','/order/deliver**','',7,current_timestamp),
       (9,'订单更新','P_ORDER_UPDATE','/order/update**','',7,current_timestamp),
       (10,'支部订单','P_ORDER_BRANCH','/order/branch**','',7,current_timestamp),
       (11,'区域支行处理','P_REGION_CONTROL','/order/region**','',7,current_timestamp),
       (12,'收货地址','P_Address','/address/**','',0,current_timestamp);

INSERT INTO sec_role_permission(id,role_id, permission_id)
VALUES (1,1,1),(2,1,2),(3,1,3),(4,1,4),(5,1,5),(6,1,6),(7,1,7),(8,1,8),(9,1,9),(10,1,10),(11,1,11),(12,1,12),
       (13,2,1),(14,2,3),(15,2,4),(16,2,5),(17,2,6),(18,2,7),(19,2,8),(20,2,9),(21,2,10),(22,2,11),(23,2,12),
       (24,3,4),(25,3,5),(26,3,6),(27,3,11),
       (28,4,5),(29,4,7),(30,4,9),(31,4,12);

-- user data--
-- create  admin--
INSERT INTO sec_user(id,username, providername, email, phone, password, hasher, salt, avatar_url, first_name, last_name, full_name, created_at)
VALUES (1,'admin','shengmu','wangrenhui1990@gmail.com','15611434500','$shiro1$SHA-256$500000$iLqsOFPx5bjMGlB0JiNjQQ==$1cPTj9gyPGmYcKGQ8aw3shybrNF1ixdMCm/akFkn71o=','default_hasher','','','管理员','圣牧','圣牧.管理员',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 (1,1,0,0,1,2,3,'人民大学',current_timestamp);

-- create user_role--
INSERT INTO sec_user_role(id, user_id, role_id)
VALUES (1,1,1);

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

public class MyJdbcAuthzService implements JdbcAuthzService {
  @Override
  public Map<String, AuthzHandler> getJdbcAuthz() {
    //加载数据库的url配置
    //按长度倒序排列url
    Map<String, AuthzHandler> authzJdbcMaps = Collections.synchronizedMap(new TreeMap<String, AuthzHandler>(
        new Comparator<String>() {
          public int compare(String k1, String k2) {
            int result = k2.length() - k1.length();
            if (result == 0) {
              return k1.compareTo(k2);
            }
            return result;
          }
        }));
    //遍历角色
    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;
  }
}

实现shiro的用户数据加载:

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);
      }
    }
  }
}

配置shiro.ini,放在resources(可放在其他目录,但必须保证能编译到classes)下,

[users]
guest = guest,guest

[main]
authc = cn.dreampie.shiro.ShiroFormAuthenticationFilter
authc.useCaptcha = false
;默认登陆数据提交路径
authc.loginUrl = /signin
#分角色登录提交配置
;authc.loginUrlMap = R_ADMIN:/admin/signin
;默认或者successUrlMap没有该角色时
authc.successUrl = /order
;不同角色登陆到不同的url,R_USER:/order可以不配置,会默认使用successUrl
authc.successUrlMap = R_USER:/order,R_MEMBER:/order/region,R_MANAGER:/order/branch,R_ADMIN:/order/branch
authc.failureUrl = /
;不同角色登陆失败跳转的路径
;authc.failureUrlMap =R_ADMIN:/admin/login

signout = cn.dreampie.shiro.ShiroLogoutFilter
;默认的退出url,redirectUrlMap里没有该角色使用该url
signout.redirectUrl = /
;如果你要区分不同角色推出到不同的url,使用map
;signout.redirectUrlMap = R_ADMIN:/admin/index

#realm
jdbcRealm = cn.dreampie.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

securityManager.sessionManager.globalSessionTimeout = 1200000
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
securityManager.sessionManager.deleteInvalidSessions = false

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

启用shiroplugin

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

//添加shiro的过滤器到interceptor
interceptors.add(new ShiroInterceptor());

//用户操作相关  使用 SubjectKit工具类

web.xml添加shiroFilter过滤

<!--权限过滤器 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-->

除了使用数据的url数据判断权限外,还可以在方法上使用注解的方式过滤权限

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

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

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

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

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

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

2.Shiro的认证注解处理是有内定的处理顺序的,如果有个多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关): RequiresRoles RequiresPermissions RequiresAuthentication RequiresUser RequiresGuest

例如:你同时使用了了RequiresRoles和RequiresPermissions,那就要求拥有此角色的同时还得拥有相应的权限。

RequiresRoles可以用在Controller或者方法上。可以多个roles,默认逻辑为 AND也就是所有具备所有role才能访问。 示例: //属于user角色 @RequiresRoles("user")

//必须同时属于user和admin角色 @RequiresRoles({"user","admin"})

//属于user或者admin之一。 @RequiresRoles(value={"user","admin"},logical=Logical.OR)

HasherKit 加密工具

User user=new User()
HasherInfo passwordInfo = HasherKit.hash(user.getStr("password"), Hasher.DEFAULT);
user.set("password", passwordInfo.getHashResult());
user.set("hasher", passwordInfo.getHasher().value());
user.set("salt", passwordInfo.getSalt());

欢迎使用与反馈意见

About

jfinal shiro plugin

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages