Jfinal与Shiro集成,有玛雅牛和dreamip两个Jfinal插件,但还是想以简单的方式实现动态URL鉴权。
本人的实现思路是,利用Shiro本身的过滤器扩展来实现动态通过数据库URL授权。方法如下:
1. 新建一个JFinal Maven项目
2. pom.xml中添加对Shiro的引用:
org.apache.shiro
shiro-all
1.2.3
3. /WEB-INF/web.xml中加入shiro的支持
org.apache.shiro.web.env.EnvironmentLoaderListener
shiroFilter
org.apache.shiro.web.servlet.ShiroFilter
shiroFilter
/*
REQUEST
FORWARD
INCLUDE
ERROR
[main]
shiro.loginUrl = /auth/login
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
dataSource = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
dataSource.serverName = localhost
dataSource.user = root
dataSource.password = root
dataSource.databaseName = jinlu
jdbcRealm.dataSource = $dataSource
jdbcRealm.authenticationQuery = SELECT password FROM sec_user WHERE status=1 AND username = ?
jdbcRealm.userRolesQuery = SELECT r.role_name FROM sec_role AS r, sec_user_role AS ur WHERE r.id = ur.role_id AND r.status=1 AND ur.user_id = (SELECT id FROM sec_user WHERE username = ?)
jdbcRealm.permissionsQuery = SELECT p.permission FROM sec_permission AS p, sec_role_permission AS rp WHERE p.id = rp.permission_id AND rp.role_id = (SELECT id FROM sec_role WHERE role_name = ?)
jdbcRealm.permissionsLookupEnabled = true
securityManager.realms = $jdbcRealm
passwordMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
passwordMatcher.hashAlgorithmName=MD5
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
securityManager.sessionManager.globalSessionTimeout = 3600000
[filters]
urlFilter=com.ziyTech.framework.interceptor.ShiroPathMatchFilter
#这里的规则,web.xml中的配置的ShiroFilter会使用到。
[urls]
/=anon
/img/**=anon
/js/**=anon
/css/**=anon
/fonts/**=anon
/uploader/**=anon
/uploads/**=anon
/auth/**=anon
/msg/**=anon
/api/**=anon
/test/*=anon
/**=urlFilter
其中[main]节中,jdbcRealm定义一个数据库认证域,根据那几个SQL,可以构造出相应的数据表。定义了一个MD5的密码加密算法,在[filters]节自定义了一个过滤ShiroPathMatchFilter,并且在[urls]节将其它非开放的URL鉴权都指向它。
5. ShiroPathMatchFilter实现对URL进行过滤
package com.ziyTech.framework.interceptor;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import com.jfinal.log.Log;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.ziyTech.framework.service.Conf;
import com.ziyTech.framework.model.SecRole;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.*;
public class ShiroPathMatchFilter extends AccessControlFilter {
private static final Log log = Log.getLog(Conf.class);
private static Multimap allPermissions = ArrayListMultimap.create();
private static List allRoles = new ArrayList();
public static void initUrlMaps(){
log.info("start initializing permission maps.");
// 缓存所有角色
allRoles.clear();
List secRoles =SecRole.dao.findAll();
for(SecRole secRole:secRoles){
allRoles.add(secRole.getStr("role_name"));
}
// 缓存所有权限
allPermissions.clear();
List rolePermissions = Db.find("select r.role_name,p.permission " +
"from sec_role r,sec_permission p,sec_role_permission rp " +
"where rp.role_id=r.id and rp.permission_id=p.id and permission is not null ");
for(Record rolePermission :rolePermissions){
allPermissions.put(rolePermission.getStr("role_name"),rolePermission.getStr("permission"));
}
log.info("finished permissions map with entries:" + allPermissions.size());
}
public boolean isAccessAllowed(Subject subject,String path){
if(allPermissions.isEmpty()){
initUrlMaps();
}
for(String role : allRoles){
if(subject.hasRole(role)){
for(String url:allPermissions.get(role)){
if(pathsMatch(url, path)){
return true;
}
}
}
}
return false;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object o) throws Exception {
if(allPermissions.isEmpty()){
initUrlMaps();
}
Subject subject = getSubject(request, response);
for(String role : allRoles){
if(subject.hasRole(role)){
for(String url:allPermissions.get(role)){
if(pathsMatch(url, request)){
return true;
}
}
}
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
log.info("onAccessDenied");
setLoginUrl("/auth/login");
redirectToLogin(request,response);
return false;
}
}
上述代码中静态变量allPermissions为MultiMap, 引这Google guava。有静态方法initUrlMaps,可在其它地方对授权信息进行初始化,如用户更改了角色,角色更改了权限时。
6. 我的鉴权相关数据库定义:
CREATE TABLE sec_user (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(50),
password VARCHAR(50),
email VARCHAR(100),
mobile VARCHAR(20),
avatar VARCHAR(200),
full_name VARCHAR(100),
status INT DEFAULT '1' NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL,
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE sec_role (
id INT NOT NULL AUTO_INCREMENT,
role_name VARCHAR(50),
description VARCHAR(200),
status INT DEFAULT '1' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL,
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE sec_permission (
id INT NOT NULL AUTO_INCREMENT,
permission VARCHAR(50) NOT NULL,
description VARCHAR(200) NOT NULL,
status INT DEFAULT '1' NOT NULL,
category VARCHAR(50),
name VARCHAR(50),
url VARCHAR(50),
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE sec_user_role (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE sec_role_permission (
id INT NOT NULL AUTO_INCREMENT,
role_id INT NOT NULL,
permission_id INT NOT NULL,
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8
7.1 sec_permission表中,如图定义权限,指定URL pattern
7.2 通过sec_role_permission将多个权限赋予一个角色
7.3 通过sec_user_role给用户赋予角色,以实现授权