shiro权限控制的简单实现
权限控制常用的有shiro、spring security,两者相比较,各有优缺点,此篇文章以shiro为例,实现系统的权限控制。
一、数据库的设计
简单的五张表,用户、角色、权限及关联表:
CREATE TABLE `sysrole` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(255) NOT NULL, `role` varchar(255) NOT NULL COMMENT '角色名', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='角色表'; CREATE TABLE `sysuser` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(250) NOT NULL, `password` varchar(250) NOT NULL, `salt` varchar(250) NOT NULL COMMENT '密码盐', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表'; CREATE TABLE `user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `role_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='用户角色表'; CREATE TABLE `syspermission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(250) NOT NULL COMMENT '权限名称', `type` int(1) NOT NULL COMMENT '权限类型,1菜单menu,2按钮button,3数据data', `permission` varchar(250) NOT NULL COMMENT '权限表达式', `parent_id` int(11) NOT NULL COMMENT '父级ID', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='权限表'; CREATE TABLE `role_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) NOT NULL, `permission_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8 COMMENT='角色权限表';
二、配置shiro
1.pom.xml文件中引入shiro的jar包
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-guice</artifactId> <version>1.3.2</version> </dependency>
2、编写shiro的配置文件
import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; @Configuration public class ShiroConfig { private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); //缓存管理器 @Bean(name = "cacheShiroManager") public EhCacheManager getCacheManager(){ return new EhCacheManager(); } //生命周期处理器 @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } //hash加密处理 @Bean(name = "hashedCredentialsMatcher") public HashedCredentialsMatcher getHashedCredentialsMatcher(){ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("MD5"); credentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); credentialsMatcher.setStoredCredentialsHexEncoded(true); return credentialsMatcher; } //浏览器会话的cookie管理 @Bean(name = "sessionIdCookie") public SimpleCookie getSessionIdCookie(){ SimpleCookie cookie = new SimpleCookie("sid"); cookie.setHttpOnly(true); cookie.setMaxAge(-1);//浏览器关闭时失效此Cookie; return cookie; } //记住我的cookie管理 @Bean(name = "rememberMeCookie") public SimpleCookie getRememberMeCookie(){ SimpleCookie cookie = new SimpleCookie("rememberMe"); //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击; cookie.setHttpOnly(true); cookie.setMaxAge(2592000);//记住我的cookie有效期30天 return cookie; } //记住我cookie管理器 @Bean public CookieRememberMeManager getRememberManager(){ CookieRememberMeManager meManager = new CookieRememberMeManager(); meManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); meManager.setCookie(getRememberMeCookie()); return meManager; } //session验证管理器 @Bean(name = "sessionValidationScheduler") public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler(){ ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler(); //设置session验证时间,15分钟一次 scheduler.setInterval(900000); return scheduler; } @Bean(name = "sessionManager") public DefaultWebSessionManager getSessionManage(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(1800000);//过期时间30分钟 //session定期验证 sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler()); sessionManager.setDeleteInvalidSessions(true); //会话cookie sessionManager.setSessionIdCookie(getSessionIdCookie()); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionValidationSchedulerEnabled(true); //session监听 LinkedList<SessionListener> list = new LinkedList<SessionListener>(); list.add(new MyShiroSessionListener()); sessionManager.setSessionListeners(list); //session的存储 EnterpriseCacheSessionDAO cacheSessionDAO = new EnterpriseCacheSessionDAO(); sessionManager.setCacheManager(getCacheManager()); sessionManager.setSessionDAO(cacheSessionDAO); return sessionManager; } @Bean(name = "myRealm") public AuthorizingRealm getShiroRealm(){ AuthorizingRealm realm = new MyShiroRealm(getCacheManager(),getHashedCredentialsMatcher()); realm.setName("my_shiro_auth_cache"); // realm.setAuthenticationCache(getCacheManager().getCache(realm.getName())); realm.setAuthenticationTokenClass(UsernamePasswordToken.class); realm.setCredentialsMatcher(getHashedCredentialsMatcher()); return realm; } @Bean(name = "securityManager") public DefaultWebSecurityManager getSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setCacheManager(getCacheManager()); securityManager.setRealm(getShiroRealm()); securityManager.setRememberMeManager(getRememberManager()); securityManager.setSessionManager(getSessionManage()); return securityManager; } @Bean public MethodInvokingFactoryBean getMethodInvokingFactoryBean(){ MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean(); factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); factoryBean.setArguments(new Object[]{getSecurityManager()}); return factoryBean; } @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(getSecurityManager()); return advisor; } @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(){ ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(getSecurityManager()); //设置登录页面路径 factoryBean.setLoginUrl("/login"); //设置登录成功跳转的路径,此方法有bug // factoryBean.setSuccessUrl("/manager/hello"); //将无需拦截的方法及页面使用anon配置 filterChainDefinitionMap.put("", "anon"); //将需认证的方法及页面使用authc配置 filterChainDefinitionMap.put("", "authc");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; } @Bean(name = "shiroDialect") public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }
3、自定义shiroRealm
import javax.annotation.Resource; import org.apache.log4j.Logger; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.thymeleaf.util.StringUtils; public class MyShiroRealm extends AuthorizingRealm { //注入查询用户信息的service层 @Resource private IAuthorityService authorityService; private Logger log = Logger.getLogger(MyShiroRealm.class); public MyShiroRealm(CacheManager cacheManager, CredentialsMatcher matcher) { super(cacheManager, matcher); } //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); SysUser user = (SysUser) principals.getPrimaryPrincipal(); for(SysRole role:user.getRoleList()){ authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken user = (UsernamePasswordToken) token; String username = user.getUsername(); String password = user.getPassword().toString(); user.isRememberMe(); if(StringUtils.isEmpty(username)){ throw new IncorrectCredentialsException("username is null"); }else if(StringUtils.isEmpty(password)){ throw new IncorrectCredentialsException("password is null"); } //根据账户名称查询用户信息 SysUser sysUser = authorityService.findByUsername(username); if(sysUser == null){ log.error("用户不存在"+username); return null; }
//密码加密策略可自定义,此处使用自定义的密码盐CredentialsSalt
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(sysUser,sysUser.getPassword(), ByteSource.Util.bytes(sysUser.getCredentialsSalt()),getName());
return authenticationInfo;
}
}
4、自定义session监听
import org.apache.log4j.Logger; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; public class MyShiroSessionListener implements SessionListener { private Logger log = Logger.getLogger(MyShiroSessionListener.class); @Override public void onStart(Session session) { log.info("----会话创建:"+session.getId()); } @Override public void onStop(Session session) { log.info("----会话停止:"+session.getId()); } @Override public void onExpiration(Session session) { log.info("----会话过期:"+session.getId()); } }
三、使用注解控制后台方法的权限
@RequiresAuthentication,认证通过可访问
@RequiresPermissions("***"),有***权限可访问
@RequiresGuest,游客即可访问
@RequiresRoles("***"),有***角色可访问
@RequiresUser("***"),是***用户可访问
四、使用shiro标签细粒度控制页面按钮及数据的权限
thyemleaf模板引入shiro标签库
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<shiro:guest/> <shiro:user/> <shiro:principal/> <shiro:hasPermission/> <shiro:lacksPermission/> <shiro:hasRole/> <shiro:lacksRole/> <shiro:hasAnyRoles/> <shiro:authenticated/> <shiro:notAuthenticated/>
使用方法参考:shiro官网