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到该目录下。

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");
  • 1
  • 2
  • 3
  • 4
  • class DefaultConfig加一个成员变量
public class DefaultConfig extends JFinalConfig {
    /**
     * 供Shiro插件使用。
     */
    Routes routes;
  • 1
  • 2
  • 3
  • 4
  • 5
  • public void configRoute(Routes me)方法中加入:
public void configRoute(Routes me) {
    this.routes = me;
    me.add(...)
...
  • 1
  • 2
  • 3
  • 4
  • 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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 配置拦截器 
    我的项目中也仅仅用到了一个全局拦截器,在某些系统中,可以只给后台需要验证的部分添加拦截器,前台部分可以不用访问控制拦截器。
public void configInterceptor(Interceptors me) {
    me.add(new ShiroInterceptor());
}
  • 1
  • 2
  • 3

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

5 配置shiro.ini

该文件需放在 /WEB-INF/shiro.ini这个位置,下面是我的shiro.ini 供参考:

[main]
#realm
dbRealm = com.learnShiro.biz.shiro.DbRealm
securityManager.realm = $dbRealm
  • 1
  • 2
  • 3
  • 4

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 需要验证身份或授权才能访问的地方,代码片段:
//需要角色是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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 解释一下Shiro共有5个注解,分别如下: 
    • RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
    • RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“guest”身份,不需要经过认证或者在原先的session中存在记录。
    • RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
    • RequiresRoles:当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
    • RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

参考文章

The end

转发 给需要的朋友

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到该目录下。

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");
  • 1
  • 2
  • 3
  • 4
  • class DefaultConfig加一个成员变量
public class DefaultConfig extends JFinalConfig {
    /**
     * 供Shiro插件使用。
     */
    Routes routes;
  • 1
  • 2
  • 3
  • 4
  • 5
  • public void configRoute(Routes me)方法中加入:
public void configRoute(Routes me) {
    this.routes = me;
    me.add(...)
...
  • 1
  • 2
  • 3
  • 4
  • 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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 配置拦截器 
    我的项目中也仅仅用到了一个全局拦截器,在某些系统中,可以只给后台需要验证的部分添加拦截器,前台部分可以不用访问控制拦截器。
public void configInterceptor(Interceptors me) {
    me.add(new ShiroInterceptor());
}
  • 1
  • 2
  • 3

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

5 配置shiro.ini

该文件需放在 /WEB-INF/shiro.ini这个位置,下面是我的shiro.ini 供参考:

[main]
#realm
dbRealm = com.learnShiro.biz.shiro.DbRealm
securityManager.realm = $dbRealm
  • 1
  • 2
  • 3
  • 4

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 需要验证身份或授权才能访问的地方,代码片段:
//需要角色是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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 解释一下Shiro共有5个注解,分别如下: 
    • RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
    • RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“guest”身份,不需要经过认证或者在原先的session中存在记录。
    • RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
    • RequiresRoles:当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
    • RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

参考文章

The end

猜你喜欢

转载自blog.csdn.net/weixin_38373006/article/details/79132972