1. 程式人生 > >Jfinal與shiro整合實現動態URL鑑權,不裝外掛只需要一個類

Jfinal與shiro整合實現動態URL鑑權,不裝外掛只需要一個類

Jfinal與Shiro整合,有瑪雅牛和dreamip兩個Jfinal外掛,但還是想以簡單的方式實現動態URL鑑權。

本人的實現思路是,利用Shiro本身的過濾器擴充套件來實現動態通過資料庫URL授權。方法如下:

1. 新建一個JFinal Maven專案

2. pom.xml中新增對Shiro的引用:

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.2.3</version>
        </dependency>
3. /WEB-INF/web.xml中加入shiro的支援
    <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>

4. resource目錄下新增配置檔案shiro.ini
[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<String,String> allPermissions = ArrayListMultimap.create();
    private static List<String> allRoles = new ArrayList<String>();

    public static void initUrlMaps(){

        log.info("start initializing permission maps.");
        // 快取所有角色
        allRoles.clear();
        List<SecRole> secRoles =SecRole.dao.findAll();
        for(SecRole secRole:secRoles){
            allRoles.add(secRole.getStr("role_name"));
        }
        // 快取所有許可權
        allPermissions.clear();
        List<Record> 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. 使用方法

7.1 sec_permission表中,如圖定義許可權,指定URL pattern

7.2 通過sec_role_permission將多個許可權賦予一個角色

7.3 通過sec_user_role給使用者賦予角色,以實現授權