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










猜你喜欢

转载自blog.csdn.net/hzw2312/article/details/54612962