spring整合shiro權限管理與數據庫設計

之前的文章中我們完成了基礎框架的搭建,現在基本上所有的後臺系統都逃不過權限管理這一塊,這算是一個剛需了。現在我們來集成shiro來達到顆粒化權限管理,也就是從連接菜單到頁面功能按鈕,都進行權限都驗證,從前端按鈕的顯示隱藏,到後臺具體功能方法的權限驗證。

首先要先設計好我們的數據庫,先來看一張比較粗糙的數據庫設計圖:


具體的數據庫設計代碼,請查看:https://git.oschina.net/gzsjd/task/blob/master/sql/task.sql?dir=0&filepath=sql

下面我們開始根據之前的框架集成shiro

首先在pom.xml添加shiro的支持,先在properties中聲明一下要倒入的版本:

<properties>
	<shiro.version>1.3.2</shiro.version>
	<commons-logging.version>1.2</commons-logging.version>
</properties>
然後在是dependency的添加:

<!-- shiro權限 -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-all</artifactId>
			<version>${shiro.version}</version>
		</dependency>
		
		<!-- commons-logging -->
		<dependency>
		    <groupId>commons-logging</groupId>
		    <artifactId>commons-logging</artifactId>
		    <version>${commons-logging.version}</version>
		</dependency>
下面是shiro的配置跟spring配置放在同級目錄spring-shiro.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	
	<!-- 緩存管理器 使用Ehcache實現 -->
	<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" />
	</bean>
	
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!--認證管理器-->
		<property name="realm" ref="shiroSecurityRealm" />
		<!-- 緩存管理器 -->
        <property name="cacheManager" ref="cacheManager" />
        <!-- rememberMe管理器 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
	</bean>
	<!-- 會話ID生成器 -->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

    <!-- 會話Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/>
    </bean>
	
	<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
	    <constructor-arg value="rememberMe"/>  
	    <property name="httpOnly" value="true"/>
	    <property name="maxAge" value="2592000"/><!-- 30天 -->  
	</bean>
	<!-- rememberMe管理器 -->
	<bean id="rememberMeManager"
		class="org.apache.shiro.web.mgt.CookieRememberMeManager">  
	    <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('7gzYfKjTASKdsai43ds==')}"/>  
	     <property name="cookie" ref="rememberMeCookie"/>
	</bean>
	
	<!-- 會話DAO -->
    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>
    <!-- 會話驗證調度器 -->
    <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        <property name="sessionValidationInterval" value="3000000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

    <!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="3000000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>
    
	<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
	    <property name="rememberMeParam" value="rememberMe"/>  
	</bean>
	
	
    <bean id="sysUserFilter" class="yfkj.gz.task.security.SysUserFilter"/>
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/page/main.action"/>
        <property name="filters">
            <util:map>
                <entry key="authc">
                    <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
                </entry>
                <entry key="sysUser" value-ref="sysUserFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
            	/static/** = anon
            	/login.jsp = anon
            	/sysuser/login.action = anon
            	/sysuser/register.action = anon
            	/sysuser/getEMailCount.action = anon
            	/sysuser/getUserNameCount.action = anon
            	/sysuser/logout.action = logout
            	/** = user,sysUser <!-- 表示訪問該地址的用戶是身份驗證通過或RememberMe登錄的都可以 -->
            	<!-- /** = authc  表示訪問該地址用戶必須身份驗證通過-->
            </value>
        </property>
    </bean>
    
    <!-- Post processor that automatically invokes init() and destroy() methods -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
</beans>

上面的

/static/** = anon,/login.jsp = anon...這些等於anon的就是默認不做權限驗證的,我們的登錄,註冊,靜態資源等,不需要權限驗證。

權限緩存的配置(如果不用緩存的話,每次請求都要去訪問數據庫查詢權限)ehcache-shiro.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shirocache">

    <diskStore path="java.io.tmpdir/yfkj-shiro-ehcache"/>
	
	<!-- 默認緩存 -->
	<defaultCache maxElementsInMemory="1000" eternal="false"
		overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
	
    <!-- 登錄記錄緩存 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

	<!-- 授權緩存 -->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

	<!-- 認證緩存 -->
    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

    <cache name="shiro-activeSessionCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>
    
    <cache name="shiro-kickout-session"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

</ehcache>
自定義用戶過濾類SysUserFilter:

import yfkj.gz.task.service.ISysUserService;

import org.apache.shiro.web.filter.PathMatchingFilter;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 自定義用戶過濾器
 * @author 胡漢三
 *
 */
public class SysUserFilter extends PathMatchingFilter {
	
	@Resource
	private ISysUserService sysUserService;

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //可以參考http://jinnianshilongnian.iteye.com/blog/2025656
    	return true;
    }
}
權限認證類ShiroSecurityRealm:

import javax.annotation.Resource;

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.authc.credential.Sha256CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

import yfkj.gz.task.dao.ISysUserDao;
import yfkj.gz.task.entity.SysRole;
import yfkj.gz.task.entity.SysUser;
import yfkj.gz.task.service.ISysUserService;

/**
 * 權限認證
 * @author 胡漢三
 * @date   2017年1月19日 上午10:52:17
 */
@SuppressWarnings("deprecation")
@Component
public class ShiroSecurityRealm extends AuthorizingRealm {
	
	@Resource
    private ISysUserService userService;

	@Resource
	private ISysUserDao sysUserDao;

	public ShiroSecurityRealm() {
		setName("ShiroSecurityRealm"); // This name must match the name in the SysUser class's getPrincipals() method
		setCredentialsMatcher(new Sha256CredentialsMatcher());
	}

	/**
	 * 登錄認證
	 */
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		SysUser user = userService.getByProerties(new String[]{"loginAccount"}, new String[]{token.getUsername()},null);
		if (user != null) {
			return new SimpleAuthenticationInfo(user.getUserId(), user.getLoginPass(), getName());
		} else {
			return null;
		}
	}


	/**
	 * 權限認證
	 */
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		Long userId = (Long) principals.fromRealm(getName()).iterator().next();
		SysUser user = userService.get(userId);
		if (user != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (SysRole role : user.getRoles()) {
				info.addRole(role.getRoleKey());
				info.addStringPermissions(role.getPermissions());
			}
			return info;
		} else {
			return null;
		}
	}

}
在web.xml加入:

<!-- 加載spring配置文件 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring.xml,classpath:spring-hibernate.xml,classpath:spring-shiro.xml</param-value>
	</context-param>
	
	<!-- shiro權限過濾器 -->
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
在登錄方法中加上權限的登錄(構造方法參數:登錄賬號,登錄密碼,記住我):

//存入session
		Subject subject = SecurityUtils.getSubject();
		//記得傳入明文密碼
		subject.login(new UsernamePasswordToken(userInfo.getLoginAccount(), user.getLoginPass(), rememberMe));
完整的登錄方法:

/**
	 * 用戶登錄
	 * @param response
	 * @param user
	 * @throws IOException
	 */
	@RequestMapping(value = "/login", method = { RequestMethod.POST, RequestMethod.GET })
	public void login(SysUser user,boolean rememberMe) throws IOException{
		//用戶登錄
		SysUser userInfo = userService.getByProerties(new String[]{"loginAccount"}, new String[]{user.getLoginAccount()},null);
		if(userInfo==null){
			result.setMessage("用戶名錯誤");
			super.writeJSON(result);
			return;
		}
		if(!userInfo.getLoginPass().equals(new Sha256Hash(user.getLoginPass()).toHex())){
			result.setMessage("密碼錯誤");
			super.writeJSON(result);
			return;
		}
		//存入session
		Subject subject = SecurityUtils.getSubject();
		//記得傳入明文密碼
		subject.login(new UsernamePasswordToken(userInfo.getLoginAccount(), user.getLoginPass(), rememberMe));
		session.setAttribute(USER_SESSION, userInfo);
		result.setMessage("登錄成功");
		result.setSuccess(true);
		super.writeJSON(result);
	}
數據庫也設計好啦,該整合的也整合了,怎麼來實現呢,這裏先說一點點,詳細的等下一篇說:

jsp頁面引入page指令:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
在要做驗證的按鈕上加上shiro標籤的判斷:

<shiro:hasPermission name="${ROLE_KEY}:role:role_add">
					<button id="btn_add" type="button" class="btn btn-default">
						<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增
					</button>
					</shiro:hasPermission>
${ROLE_KEY}:role:role_add的意思就是:

${ROLE_KEY}角色

role是指菜單(頁面)

role_add指的功能

聯合起來就是,當前角色在role菜單(頁面)中有沒有role_add新增的功能,如果有就會顯示,沒有就不顯示這個按鈕啦。

在後臺方法中驗證:

在對應的方法中加入代碼:

Subject subject = SecurityUtils.getSubject();
subject.checkPermission(getCurrentRoleKey()+":role:role_add");
如果沒有通過checkPermission,則會直接返回錯誤,不執行下面的代碼啦。


實體Base類BaseEntity:

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 實體父類
 * @author 胡漢三
 * @date   2017年1月18日 上午11:03:11
 */
public class BaseEntity implements Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 3730369554400423966L;
	
	/**
	 * 排序
	 */
	private Map<String, String> sortedConditions = new LinkedHashMap<String, String>();

	public Map<String, String> getSortedConditions() {
		return sortedConditions;
	}
	public void setSortedConditions(Map<String, String> sortedConditions) {
		this.sortedConditions = sortedConditions;
	}
	
	
}

用戶實體SysUser:

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import yfkj.gz.support.BaseEntity;


/**
 * 用戶的實體類
 */
@Entity
@Table(name = "sys_user")
public class SysUser extends BaseEntity{

	/**
	 * 
	 */
	private static final long serialVersionUID = 2491111485758197830L;
	
	/**主鍵**/
    @Id
    @GeneratedValue
    @Column(name = "user_id")
    private Long userId;

    /**登錄賬號**/
    @Column(name = "login_account" ,length = 30 , unique = true )
    private String loginAccount;

    /**登錄密碼**/
    @Column(name = "login_pass" ,length = 65)
    private String loginPass;

    /**暱稱**/
    @Column(name = "user_name" ,length = 20)
    private String userName;

    /**頭像**/
    @Column(name = "user_head" ,length = 30)
    private String userHead;

    /**手機**/
    @Column(name = "user_phone" ,length = 20)
    private String userPhone;

    /**郵箱**/
    @Column(name = "user_email" ,length = 30)
    private String userEmail;

    /**性別**/
    @Column(name = "user_sex")
    private Integer userSex;

    /**生日**/
    @Column(name = "user_birthday" ,length = 30)
    private String userBirthday;

    /**註冊時間**/
    @Column(name = "register_time" ,length = 30)
    private String registerTime;
    
    /**部門編碼**/
    @Column(name = "department_key" ,length = 20)
    private String departmentKey;
    
    
    /**用戶角色**/
    @ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "sys_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") })
	@Cache(region = "all", usage = CacheConcurrencyStrategy.READ_WRITE)
	private Set<SysRole> roles = new HashSet<SysRole>();
    
    
    /**get/set**/
    

    /**主鍵**/
    public Long getUserId(){
        return userId;
    }
    /**主鍵**/
    public void setUserId(Long userId){
        this.userId= userId;
    }
    /**登錄賬號**/
    public String getLoginAccount(){
        return loginAccount;
    }
    /**登錄賬號**/
    public void setLoginAccount(String loginAccount){
        this.loginAccount= loginAccount;
    }
    /**登錄密碼**/
    public String getLoginPass(){
        return loginPass;
    }
    /**登錄密碼**/
    public void setLoginPass(String loginPass){
        this.loginPass= loginPass;
    }
    /**暱稱**/
    public String getUserName(){
        return userName;
    }
    /**暱稱**/
    public void setUserName(String userName){
        this.userName= userName;
    }
    /**頭像**/
    public String getUserHead(){
        return userHead;
    }
    /**頭像**/
    public void setUserHead(String userHead){
        this.userHead= userHead;
    }
    /**手機**/
    public String getUserPhone(){
        return userPhone;
    }
    /**手機**/
    public void setUserPhone(String userPhone){
        this.userPhone= userPhone;
    }
    /**郵箱**/
    public String getUserEmail(){
        return userEmail;
    }
    /**郵箱**/
    public void setUserEmail(String userEmail){
        this.userEmail= userEmail;
    }
    /**性別**/
    public Integer getUserSex(){
        return userSex;
    }
    /**性別**/
    public void setUserSex(Integer userSex){
        this.userSex= userSex;
    }
    /**生日**/
    public String getUserBirthday(){
        return userBirthday;
    }
    /**生日**/
    public void setUserBirthday(String userBirthday){
        this.userBirthday= userBirthday;
    }
    /**註冊時間**/
    public String getRegisterTime(){
        return registerTime;
    }
    /**註冊時間**/
    public void setRegisterTime(String registerTime){
        this.registerTime= registerTime;
    }
    
	public Set<SysRole> getRoles() {
		return roles;
	}
	public void setRoles(Set<SysRole> roles) {
		this.roles = roles;
	}
    
	
}

角色實體SysRole:

import java.util.Set;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;


import yfkj.gz.support.BaseEntity;

/**
 * 角色的實體類
 */
@Entity
@Table(name = "sys_role")
@Cache(region = "all", usage = CacheConcurrencyStrategy.READ_WRITE)
public class SysRole extends BaseEntity{

	// 各個字段的含義請查閱文檔的數據庫結構部分
	private static final long serialVersionUID = 6019103858711599150L;
	@Id
	@GeneratedValue
	@Column(name = "role_id")
	private Long roleId;
	@Column(name = "role_key", length = 40, nullable = false, unique = true)
	private String roleKey;
	@Column(name = "role_value", length = 40, nullable = false)
	private String roleValue;
	@Column(name = "create_time", length = 30)
	private String createTime;
	@Column(name = "description", length = 200)
	private String description;
	
	@ElementCollection
	@JoinTable(name = "sys_role_permission", joinColumns = { @JoinColumn(name = "role_id") })
	@Cache(region = "all", usage = CacheConcurrencyStrategy.READ_WRITE)
	private Set<String> permissions;

	@Column(name="company_id")
	private Long companyId;
	
	public SysRole() {

	}

	

	public Long getRoleId() {
		return roleId;
	}

	public void setRoleId(Long roleId) {
		this.roleId = roleId;
	}

	public String getRoleKey() {
		return roleKey;
	}

	public void setRoleKey(String roleKey) {
		this.roleKey = roleKey;
	}

	public String getRoleValue() {
		return roleValue;
	}

	public void setRoleValue(String roleValue) {
		this.roleValue = roleValue;
	}

	public String getCreateTime() {
		return createTime;
	}

	public void setCreateTime(String createTime) {
		this.createTime = createTime;
	}
	
	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Set<String> getPermissions() {
		return permissions;
	}

	public void setPermissions(Set<String> permissions) {
		this.permissions = permissions;
	}

	public Long getCompanyId() {
		return companyId;
	}

	public void setCompanyId(Long companyId) {
		this.companyId = companyId;
	}

}
項目結構圖:


源碼地址:https://git.oschina.net/gzsjd/task










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