自定义标签 + shiro 实现权限细粒度控制

Posted node2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义标签 + shiro 实现权限细粒度控制相关的知识,希望对你有一定的参考价值。

这里我们是使用shiro来实现登录验证,授权等操作,然后利用自定义jsp标签来实现权限菜单的细力度控制。所谓的细粒度控制,就是根据用户登录权限的不同,显示不同的菜单,例如,用户如果有添加用户,修改用户的权限,我们就显示这个俩个菜单,然后我们并不显示删除用户的菜单。

如何自定义jsp标签

1.定义一个权限标签,命名为mytag.tld

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <description>/</description>
    <display-name>Permission Tag Library</display-name>
    <tlib-version>1.0</tlib-version>
    <short-name>m</short-name>
    <uri>/my-tags</uri>
    <tag>
        <description>权限控制标签</description>
        <name>auth</name>
        <tag-class>com.xxx.core.tag.PrivilegeTag</tag-class>  <!--标签控制类-->
        <body-content>JSP</body-content>
        <attribute>
            <name>privilege</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>String</type>
        </attribute>
    </tag>
</taglib>

2.将该xml文件放于/WEB-INF/tlds下,并实现标签控制类

/**
 * 重写权限标签控制类
 * @author liuxg
 * @date 2015年8月24日 上午9:13:15
 */
public class PrivilegeTag extends TagSupport {

    private static final long serialVersionUID = 1L;
    private String privilege; //标签属性

    @Override
    public int doStartTag() {
        User user =  AuthUtil.getCurrentUser();//获取登录用户信息
        if(user == null) return SKIP_BODY;
        if (isManager(user)) return EVAL_BODY_INCLUDE;  //超级管理员获取所有权限
        boolean bResult = SecurityUtils.getSubject().isPermitted(privilege);//根据标签属性判断用户是否有此菜单功能权限,isPermitted的调用会触发doGetAuthorizationInfo方法
        if(bResult){
            return EVAL_BODY_INCLUDE;
        }
        return SKIP_BODY;
    }


    /**
     * 判断用户是否超级管理员
     * @return
     */
    private boolean isManager(User user){

        List<Role> roles = user.getRoles();
        boolean b = false ;
        for (Role role : roles) { //遍历是否有超级管理员角色
            if (role.getIsManager() == Constants.MANAGER_CODE) {
                b = true ;
                break ;
            }
        }
        String accountName = user.getAccountName();
        if (accountName.equals(Constants.ADMIN_ACCOUNT) 
                || accountName.equals(Constants.SYSADMIN_ACCOUNT) 
                || b) {
            return true;
        }
        return false;

    }

    public String getPrivilege() {
        return privilege;
    }

    public void setPrivilege(String privilege) {
        this.privilege = privilege;
    }
}

3.接下来还需要在web.xml设置我们自定义的标签路径

<jsp-config>
        <taglib>
            <taglib-uri>/my-tags</taglib-uri>
            <taglib-location>/WEB-INF/tlds/my-tag.tld</taglib-location>
        </taglib>
    </jsp-config>

到此我们就完成了标签的自定义,那在jsp页面上,我们只需要引入我们自定义的标签库,并在需要控制的html标签上使用我们的权限标签即可

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="m" uri="/gosun-tags" %><!--引入我们的标签-->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>头部</title>
</head>
<body>
//...
           <m:auth privilege="aaa"> <!--我们自定义标签-->
                <li >
                  //...
                </li>
           </m:auth>
            <m:auth privilege="bbb">
                <li >
                    //...
                </li>
            </m:auth>
            <m:auth privilege="ccc">
                <li >
                    //...
                </li>
            </m:auth>
        </ul> 
    </div>
//...
</body>

通过<m:auth></m:auth>包裹的html标签,我们可以在实现类中,通过代码控制显示与否。

使用shiro来进行用户的权限登录和授权

1.因为web项目而且使用spring,我们就先把shiro和spring整合起来吧。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
        ">
    <!-- 自定义一个realm校验  -->
    <bean id="gipsRealm" class="com.xxx.gips.realm.GIPSRealm" />

    <!-- 自定义一个form校验拦截器   -->
    <bean id="formAuthenticationFilter" class="com.xxxx.gips.realm.CustomFormAuthenticationFilter">
        <property name="usernameParam" value="username" />
        <property name="passwordParam" value="password" />
        <property name="loginUrl" value="/loginsubmit.do" />
        <property name="rememberMeParam" value="rememberMe" />
    </bean>
     <!-- sessionManager管理器 ,采用shiro默认的缓存策略 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />

    <!-- rememberMeManager管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie" />
    </bean>

    <!-- rememberMeCookie管理器 -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <property name="maxAge" value="-1" />
        <property name="httpOnly" value="true"/>
    </bean>

    <!-- securityManager管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="gipsRealm" />
        <property name="cacheManager" ref="cacheManager" /> 
         <property name="rememberMeManager" ref="rememberMeManager" /> 
    </bean>

    <!-- 具体的 路径拦截器,登录验证路径在这里拦截 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login/login.jsp" />
        <property name="successUrl" value="/backer/auth/homePage" />
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /front/** = anon  <!-- 不需要拦截的路径   -->
                /loginsubmit.do = authc <!-- 用 户 必 须 身 份 验 证 通 过 ,将触发doGetAuthenticationInfo -->
                /logout.do = logout <!-- 退出操作 -->
                /backer/** = user <!--已经登录验证通过或者通过rememberMe  -->
            </value>
        </property>
    </bean>

    <!-- 开启注解功能,验证失败, 其会抛出 UnauthorizedException异常, 此时可以使用 Spring的 ExceptionHandler处理-->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

</beans>

这是spring-shiro的配置文件,需要加入到web.xml的contextConfigLocation里面,接下来看看怎么写登录的jsp页面

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>

  <form action="${pageContext.request.contextPath}/loginsubmit.do" method="post" ><!--action的路径在spring-shiro可配置  -->
        用户名:<input type = "text"  name = "username"/><br><br>
        密&nbsp;码:<input type = "password"  name = "password"/><br><br>
     <label>保存密码<input type = "checkbox" name = "rememberMe" /></label>
     <input type = "submit" value = "登录" />
  </form>
</body>
</html>

2.定义Realm,进行权限验证和登录授权

import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.gosun.backer.permission.entity.Permission;
import com.gosun.backer.role.entity.Role;
import com.gosun.backer.user.entity.User;
import com.gosun.backer.user.service.UserMgrService;
import com.gosun.core.utils.Constants;
import com.gosun.util.auth.AuthUtil;


/**
 * 用户登录进去的域
 * @author liuxg
 * @date 2016年5月30日 下午4:02:06
 */
public class GIPSRealm extends AuthorizingRealm {

    @Autowired UserMgrService userService;

    /**
     * 对当前路径予权限和角色,因为配置ehcache,所以可以把用户权限角色信息缓存起来
     * 当用户调用Security.getSubject().isPermitted(permissions),ecurity.getSubject().hasRole(roleIdentifier)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        User user = AuthUtil.getCurrentUser();
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();  
        simpleAuthorInfo.addRoles(getRoles(user));
        simpleAuthorInfo.addStringPermissions(getPermCodes(user));
        return simpleAuthorInfo;

    }



    /**
     * 获取权限,string存放的是权限编码
     * @param user
     * @return
     */
    private List<String> getPermCodes(User user) {

        List<String> perms = new ArrayList<String>();
        List<Role> roles = user.getRoles();
        for (Role role : roles) {
            List<Permission> _perms = role.getPermissions();
            for (Permission _perm : _perms) {
                perms.add(_perm.getPermCode());
            }
        }
        return perms;
    }


    /**
     * 获取角色集合,string存放的角色名称
     * @param user
     * @return
     */
    private List<String> getRoles(User user) {

        List<String> roles = new ArrayList<String>();
        for (Role role : user.getRoles()) {
            roles.add(role.getRoleName());
        }
        return roles;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken)authcToken; 
        User user = userService.findByAccountName(token.getUsername()) ;//通过帐号获取用户实例

        if (user != null && ByteSource.Util.bytes(token.getPassword())
                .equals(ByteSource.Util.bytes(user.getPassword()))) {//用户校验
            setSessionInfo(user);
            return  new SimpleAuthenticationInfo(user.getAccountName(), user.getPassword(), user.getNickName());   //验证成功之后进行授权
        }

        return null ;

    }


    /**
     * 存放一些信息到session中,便于获取,可以通过httpsession获取相应的信息
     * @param user
     */
    @SuppressWarnings("unused")
    private void setSessionInfo(User user){

        Subject sub = SecurityUtils.getSubject();
        Session session = sub.getSession();

        //显示的设置权限和角色,避免下次再去数据库获取,提高效率
        List<Role> roles = user.getRoles();
        for (int i = 0; i < roles.size(); i++) {
            Role role = roles.get(i);
            List<Permission> perms = role.getPermissions();
            for (Permission permission : perms) {}
            role.setPermissions(perms);
            roles.set(i,role);
        }
        user.setRoles(roles);

        session.setAttribute(Constants.CURRENT_USER, user);
    }
}

这里我们还可以自定义一个form拦截器,可以在验证之前做一些东西,例如验证码验证等等

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;


/**
 * 自定义登录拦截器,可以在shiro调用自身登录之前做一些操作
 * @author liuxg
 * @date 2016年5月30日 下午8:10:56
 */
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{


     @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
            throws Exception {
        return super.onAccessDenied(request, response);
    }
}

接下来,我们来看看,定义的几个实体类,权限,角色和用户实体类
首先是我们的用户实体类

/**
 * 用户实体类
 * @author liuxg
 * @date 2016年6月1日 下午2:36:13
 */
@SuppressWarnings("serial")
@Entity
@Where(clause = "is_deleted = 0")
@Table(name = "tb_user")
public class User extends IdEntity{

    private String accountName ;
    private String password ;
    private String nickName ;
    private String mobilePhone ;
    private String email ;
    private Byte isDeleted ;
    private Date createTime ;
    private List<Role> roles ;


    public User(String accountName, String password, String nickName, String mobilePhone, String email, Byte isDeleted,
            Date createTime) {
        super();
        this.accountName = accountName;
        this.password = password;
        this.nickName = nickName;
        this.mobilePhone = mobilePhone;
        this.email = email;
        this.isDeleted = isDeleted;
        this.createTime = createTime;
    }


    public User(){} ;


    @Column(name = "account_name")
    public String getAccountName() {
        return accountName;
    }
    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }
    @Column(name = "password")
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Column(name = "mobile_phone")
    public String getMobilePhone() {
        return mobilePhone;
    }
    public void setMobilePhone(String mobilePhone) {
        this.mobilePhone = mobilePhone;
    }
    @Column(name = "email")
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }

    @Column(name = "is_deleted")
    public Byte getIsDeleted() {
        return isDeleted;
    }

    public void setIsDeleted(Byte isDeleted) {
        this.isDeleted = isDeleted;
    }

    @Column(name = "create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @ManyToMany(mappedBy = "users")
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Column(name = "nick_name")
    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }   
}

再然后是角色实体类


/**
 * 角色实体类
 * @author liuxg
 * @date 2016年6月1日 下午2:35:37
 */
@SuppressWarnings("serial")
@Entity
@Where(clause = "is_deleted = 0")
@Table(name = "tb_role")
public class Role extends IdEntity {

    private String roleName ;
    private String roleDesc ;
    private Byte isDeleted ;
    private Byte isManager ;
    private Date createTime ;
    private List<Permission> permissions ;
    private List<User> users ;

    public Role(String roleName, String roleDesc, Byte isDeleted, Byte isManager, Date createTime) {
        super();
        this.roleName = roleName;
        this.roleDesc = roleDesc;
        this.isDeleted = isDeleted;
        this.isManager = isManager;
        this.createTime = createTime;
    }

    public Role(){} ;

    @Column(name = "role_name")
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    @Column(name = "role_desc")
    public String getRoleDesc() {
        return roleDesc;
    }
    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }
    @Column(name = "is_deleted")
    public Byte getIsDeleted() {
        return isDeleted;
    }
    public void setIsDeleted(Byte isDeleted) {
        this.isDeleted = isDeleted;
    }
    @Column(name = "is_manager")
    public Byte getIsManager() {
        return isManager;
    }
    public void setIsManager(Byte isManager) {
        this.isManager = isManager;
    }

    @Column(name = "create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }


    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name="tb_user_role_conf", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
    inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="id") }) 
    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }


    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name="tb_role_permission_conf", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")},
    inverseJoinColumns={@JoinColumn(name="permisson_id",referencedColumnName="id") }) 
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

最后是权限实体类

/**
 * 权限菜单实体类
 * @author liuxg
 * @date 2016年6月1日 下午2:35:37
 */
@SuppressWarnings("serial")
@Entity
@Table(name = "tb_permission")
public class Permission extends IdEntity{

    private String permName ;
    private String permCode ;
    private String permPath ;
    private Byte permType ;         //权限类型 0一级菜单 1二级菜单 2功能菜单(功能菜单可以没有路径)
    private Integer orderNo ;      //菜单排序号,关系用户登录时候,关系到首页显示具体哪个菜单
    private Permission parent ;
    private List<Role> roles ; 

    public Permission(String permName, String permPath, Byte permType ,Permission parent) {
        super();
        this.permType = permType ;
        this.permName = permName;
        this.permPath = permPath;
        this.parent = parent;
    }

    public Permission(){};

    @Column(name= "perm_name")
    public String getPermName() {
        return permName;
    }
    public void setPermName(String permName) {
        this.permName = permName;
    }
    @Column(name= "perm_path")
    public String getPermPath() {
        return permPath;
    }
    public void setPermPath(String permPath) {
        this.permPath = permPath;
    }

    @OneToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="parent_id")
    public Permission getParent() {
        return parent;
    }
    public void setParent(Permission parent) {
        this.parent = parent;
    }

    @ManyToMany(mappedBy = "permissions")
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    @Column(name= "perm_code")
    public String getPermCode() {
        return permCode;
    }

    public void setPermCode(String permCode) {
        this.permCode = permCode;
    }

    @Column(name= "perm_type")
    public Byte getPermType() {
        return permType;
    }

    public void setPermType(Byte permType) {
        this.permType = permType;
    }

    @Column(name= "order_no")
    public Integer getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(Integer orderNo) {
        this.orderNo = orderNo;
    }

}

这里还有一个问题,就是当当用户登录,有某些标签权限,那用户登录之后,就必须根据这些权限,路由到某个路径下,这里就需要做一个权限路径的路由方法。

/**
 * 后台主页路径导航controller
 * @author liuxg
 * @date 2016年5月30日 下午5:56:58
 */
@RestController
@RequestMapping("/backer/auth")
public class AuthController {

    @Autowired AuthService loginService;

    /**
     * 登录成功之后进来这里,根据用户的角色和权限进行菜单路径路由
     * @return
     */
    @RequestMapping("/homePage")
    public ModelAndView homePage() {
        List<Permission> perms = AuthUtil.getCurrentUserPermissions();
        String path = perms.get(0).getPermPath();
        return new ModelAndView(path); //这里假设路由到user
    }

}

这里我们的AuthUtil这么定义的

/**
 * httpClient工具类
 * @author liuxg
 * @date 2016年6月1日 下午6:29:07
 */
public class AuthUtil {


    /**
     * 获取当前用户
     * @return
     */
    public static User  getCurrentUser(){
        Subject sub = SecurityUtils.getSubject();
        Session session = sub.getSession();
        User user = (User) session.getAttribute(Constants.CURRENT_USER);
        return user ;
    }


    /**
     * 在用户中获取权限信息
     * @return
     */
    public static List<Permission>  getCurrentUserPermissions() {

        User user = AuthUtil.getCurrentUser();
        List<Permission> perms = new ArrayList<Permission>();
        for (Role role : user.getRoles()) {
            for (Permission permission : role.getPermissions()) {
                perms.add(permission);
            }
        }

        perms = filterRepAndSort(perms);

        return perms;
    }


    /**
     * 去重,排序
     * @param perms
     * @return
     */
    private static List<Permission> filterRepAndSort(List<Permission> perms) {

         for ( int i = 0 ; i < perms.size() - 1 ; i++ ) {  
             for ( int j = perms.size() - 1 ; j > i; j-- ) {  
               if (perms.get(j).getOrderNo() == perms.get(i).getOrderNo()) {  
                   perms.remove(j);  
               }   
              }   
            }   

        Collections.sort(perms, new Comparator<Permission>() {

            @Override
            public int compare(Permission p1, Permission p2) {
                return p1.getOrderNo() - p2.getOrderNo();
            }

        });

        return perms;

    }
}

功能就暂时实现到这里,这里细粒度的作用,可以让我们把权限限制到html的任何一个标签上,用户没有权限的标签,我们可以直接隐藏,不给用户操作,用户也不可见。请多指教

以上是关于自定义标签 + shiro 实现权限细粒度控制的主要内容,如果未能解决你的问题,请参考以下文章

了解权限控制框架shiro 之实际应用.

Shiro介绍

Shiro入门这篇就够了Shiro的基础知识回顾URL拦截

shiro源码分析-授权过程

shiro权限控制有哪几张数据表

shiro--颗粒度