自定義標籤 + 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的任何一個標籤上,用戶沒有權限的標籤,我們可以直接隱藏,不給用戶操作,用戶也不可見。請多指教

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章