1. 程式人生 > >Jfinal配合Shiro進行許可權控制

Jfinal配合Shiro進行許可權控制

web專案總免不了使用者的管理與註冊,需求稍微再多一點兒,就涉及使用者的角色及許可權管理了,下面根據自己專案的實際經驗,介紹如何在Jfinal專案中使用Shiro來進行簡單的登陸及許可權管理。

主角簡介

  1. Jfinal 位居開源中國年度熱門開源專案前列,簡單好用快速的java web開發框架,用過就知道。
  2. Shiro Apache基金會頂級專案,所以你懂得。java安全框架裡的主流選擇,號稱相當簡單,但是我至今其實對一些概念還稀裡糊塗,所以本文也只記錄使用,不做原理概念分析,入門參見教程

使用

1 方案選擇

根據專案需求設計角色及許可權管理方案,我用到的幾乎是最簡單的了,如下圖所示:
db_design

2 引入shiro

  • 新增shiro-core-1.2.4.jarshiro-web-1.2.4.jar至專案WEB-INF/lib目錄下,同時確保shiro的依賴jar:slf4j,commons-beanutils,commons-logging也位於該目錄下(maven直接pom新增上面兩個shiro的依賴就好)。
  • 新增Jfinal shiro外掛jfinal-shiro-2.0.0.jar到該目錄下。

3 DefaultConfig.java

  • DefaultConfig.java中的public void configConstant(Constants me)方法中加入401與403錯誤程式碼處理(可選)。
//RequiresGuest,RequiresAuthentication,RequiresUser驗證異常,返回HTTP401狀態碼
me.setErrorView(401, "/login.html");
//RequiresRoles,RequiresPermissions授權異常,返回HTTP403狀態碼
me.setErrorView(403, "/login.html");
  • class DefaultConfig加一個成員變數
public class DefaultConfig extends JFinalConfig {
    /**
     * 供Shiro外掛使用。
     */
Routes routes;
  • public void configRoute(Routes me)方法中加入:
public void configRoute(Routes me) {
    this.routes = me;
    me.add(...)
...
  • public void configPlugin(Plugins me)方法最後加入:
public void configPlugin(Plugins me) {
    ...//other plugins
    ShiroPlugin shiroPlugin = new ShiroPlugin(this.routes);
    shiroPlugin.setLoginUrl("/login.do");//登陸url:未驗證成功跳轉
    shiroPlugin.setSuccessUrl("/index.do");//登陸成功url:驗證成功自動跳轉
    shiroPlugin.setUnauthorizedUrl("/login/needPermission");//授權url:未授權成功自動跳轉
    me.add(shiroPlugin);
}
  • 配置攔截器
    我的專案中也僅僅用到了一個全域性攔截器,在某些系統中,可以只給後臺需要驗證的部分新增攔截器,前臺部分可以不用訪問控制攔截器。
public void configInterceptor(Interceptors me) {
    me.add(new ShiroInterceptor());
}

4 實現Realm

下面是我的程式碼供參考

package com.learnShiro.biz.shiro;

import ...

public class DbRealm extends AuthorizingRealm {
    public String getName() {
        return "DbRealm";
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        Model m = (Model) principals.fromRealm(getName()).iterator().next();
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();

        if( null == m){
            return info;
        }

        Roles role=Roles.dao.findFirst("select * from roles where id = ? limit 1", m.getInt("roleid"));
        if( null == role){
            return info;
        }

        info.addRole(role.getStr("rolename"));

        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        CaptchaUsernamePasswordToken authcToken = (CaptchaUsernamePasswordToken) token;

        if (authcToken.getUsername()==null||StrKit.isBlank(authcToken.getUsername())) {
            throw new AuthenticationException("使用者名稱不可以為空");
        }

        String loginName=authcToken.getUsername();

        String extraStr=authcToken.getExtra();
        if (StringUtils.equals(extraStr, "admin")) {
            Admin admin = Admin.dao.findFirst("select * from admin where loginname = ? and islock=0 limit 1",loginName);
            if (null == admin) {
                throw new AuthenticationException("使用者名稱或者密碼錯誤");
            }else{
                return new SimpleAuthenticationInfo(admin, admin.getStr("loginpass"), getName());
            }
        }else {
            Students student = Students.dao.findFirst("select * from students where loginname = ? and enable =1 limit 1",loginName);
            if (null == student) {
                throw new AuthenticationException("使用者名稱或者密碼錯誤");
            }else{
                return new SimpleAuthenticationInfo(student, student.getStr("loginpass"), getName());
            }
        }   
    }

}

5 配置shiro.ini

該檔案需放在 /WEB-INF/shiro.ini這個位置,下面是我的shiro.ini 供參考:

[main]
#realm
dbRealm = com.learnShiro.biz.shiro.DbRealm
securityManager.realm = $dbRealm

6 配置web.xml

在所有filter前面加上shiro的filter

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
    <filter-name>shiro</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>shiro</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

7 繼續

已經囉囉嗦嗦的配置這麼多了,繼續幹嘛?趕緊開始用啊!!!

  • 登陸時驗證使用者名稱密碼
public void doLogin() {
    String username = getPara("loginName");
    String password = getPara("password");

    try {
        password = LoginUtils.genEncryptPass(password, username);

        String rememberMeStr = getPara("rememberMe");
        boolean rememberMe=false;
        if (StringUtils.equals(rememberMeStr, "on")) {
            rememberMe=true;
        }

        //驗證碼
        if (!validateCaptcha("captcha")) {
            this.setAttr("loginError", "驗證碼錯誤");
            this.keepPara();
            this.forwardAction("/login");
            return;
        }

        CaptchaUsernamePasswordToken token = new 
                CaptchaUsernamePasswordToken(username, password,rememberMe,"","",extraStr);

        Subject subject = SecurityUtils.getSubject();

        // 進行用使用者名稱和密碼驗證,如果驗證不過會throw exception
        subject.login(token);

        if (extraStr.equals("admin")) {
            //save admin session
            Admin admin = Admin.dao.findFirst("select * from admin where loginname = ? limit 1",username);
            setSessionAttr("admin", admin);

            // 調轉到admin主頁面
            this.redirect("/admin");
        }else {
            //save student session
            Students student = Students.dao.findFirst("select * from students where loginname = ? limit 1",username);
            setSessionAttr("student", student);

            // 調轉到user主頁面
            this.redirect("/user");
        }

    } catch (Exception e) {
        this.setAttr("loginError", "使用者名稱或密碼錯誤");
        this.keepPara();
        if (extraStr.equals("admin")) {
            this.forwardAction("/login/adminLogin");
        }else{
            this.forwardAction("/login");
        }
    }
}
  • 需要驗證身份或授權才能訪問的地方,程式碼片段:
//需要角色是admin和teacher的才能訪問,否則跳轉至授權url
@RequiresRoles(value = { "admin","teacher" },logical=Logical.OR)
public class AdminController extends Controller {
    public void index() {
        //...
    }
}
///////////////////////
//需要通過驗證(登陸成功)才能訪問,否則跳轉至登陸url
@RequiresAuthentication
public void logout() {
    Subject currentUser = SecurityUtils.getSubject();
    try {
        currentUser.logout();
        this.redirect("/login");
    } catch (Exception e) {
        log.debug("登出發生錯誤", e);
    }
}
  • 解釋一下Shiro共有5個註解,分別如下:
    • RequiresAuthentication:使用該註解標註的類,例項,方法在訪問或呼叫時,當前Subject必須在當前session中已經過認證。
    • RequiresGuest:使用該註解標註的類,例項,方法在訪問或呼叫時,當前Subject可以是“guest”身份,不需要經過認證或者在原先的session中存在記錄。
    • RequiresPermissions:當前Subject需要擁有某些特定的許可權時,才能執行被該註解標註的方法。如果當前Subject不具有這樣的許可權,則方法不會被執行。
    • RequiresRoles:當前Subject必須擁有所有指定的角色時,才能訪問被該註解標註的方法。如果當天Subject不同時擁有所有指定角色,則方法不會執行還會丟擲AuthorizationException異常。
    • RequiresUser:當前Subject必須是應用的使用者,才能訪問或呼叫被該註解標註的類,例項,方法。

參考文章

The end