這裏我們是使用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>
密 碼:<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的任何一個標籤上,用戶沒有權限的標籤,我們可以直接隱藏,不給用戶操作,用戶也不可見。請多指教