上一章已经完整的搭建好了一个框架,现在需要集成shiro来达到前后端分离之后,对用户的权限控制
第一步,建立管理的一系列表sql如下
/* Navicat Premium Data Transfer Source Server : diss Source Server Type : MySQL Source Server Version : 50725 Source Host : rm-bp14to8mn841swfk6so.mysql.rds.aliyuncs.com:3306 Source Schema : wy Target Server Type : MySQL Target Server Version : 50725 File Encoding : 65001 Date: 12/10/2019 10:46:41 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_permission`; CREATE TABLE `sys_permission` ( `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限编码,主键', `flag` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识', `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称', `order_by` int(11) NULL DEFAULT NULL COMMENT '排序', `parent_code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上级权限', `type` int(11) NULL DEFAULT NULL COMMENT '类型,0菜单,1按钮', `url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '访问路径', PRIMARY KEY (`code`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_permission -- ---------------------------- INSERT INTO `sys_permission` VALUES ('0001', 'admin:user:list', '用户列表', 1, NULL, NULL, NULL); INSERT INTO `sys_permission` VALUES ('0002', 'admin:user:info', '用户详情', 1, NULL, NULL, NULL); -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `id`(`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES (1, '总管理'); INSERT INTO `sys_role` VALUES (2, '审核管理'); INSERT INTO `sys_role` VALUES (3, '财务管理'); INSERT INTO `sys_role` VALUES (4, '商城管理'); INSERT INTO `sys_role` VALUES (5, '客服'); -- ---------------------------- -- Table structure for sys_role_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_role_permission`; CREATE TABLE `sys_role_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `permission_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限', `role_id` int(11) NULL DEFAULT NULL COMMENT '角色', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `id`(`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_role_permission -- ---------------------------- INSERT INTO `sys_role_permission` VALUES (1, '0001', 1); INSERT INTO `sys_role_permission` VALUES (2, '0001', 1); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '管理用户', `address` varchar(0) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `create_time` datetime(0) NULL DEFAULT NULL, `email` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `sex` int(11) NULL DEFAULT NULL, `real_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实姓名', PRIMARY KEY (`username`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES ('admin', NULL, '2019-04-19 11:25:03', NULL, '123456', NULL, NULL, NULL); -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) NULL DEFAULT NULL COMMENT '角色', `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '系统用户', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `id`(`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (1, 1, 'admin'); SET FOREIGN_KEY_CHECKS = 1;
其中,权限控制共五张表,sys_user是系统用户表,sys_role是角色表,sys_user_role是用户角色表(用来表示这个用户有哪几种角色),sys_permission是系统权限表,sys_role_permission是角色权限表(这个角色有哪几种权限)
关于上面五张表的关系,可以用以下例子来解释,如果还是不懂可以百度一下,系统角色与权限之间的关系
某公司一个系统中,有增删改查四种权限,公司董事长可以操作所有权限,公司总经理可以操作增改查三种权限,业务员可以操作查询权限
其中增删改查四种权限,在sys_permission表中表现为四条数据
sys_role就有三条数据,董事长、总经理、业务员
那么sys_role_permission表中董事长关联了四条数据,总经理关联了三条数据,业务员关联了一条数据,表示各自所具有的功能权限
现在A用户就任公司董事长,B用户就任总经理,C用户就任业务员
sys_user中设置一个管理用户为 A ,B用户和C用户
此时 在sys_user_role中将A用户关联董事长,那么A用户就可以操作增删改查四条功能,B用户关联总经理那么B用户就可以操作三条功能,C用户同理
如果此时B用户抓住了A用户的小辫子,把他拉下台了,两人的位置互换,B用户关联董事长,A用户关联总经理,那么B用户就可以操作四条数据,A用户只能操作三条数据
这么做的好处在与可以将权限的细粒度划分的极为细小,并且操作方便。
上面讲述了权限表之间的关系,回到正题,将以上五张表,使用generator导入到项目中
此时我们进行第二步
配置shiro的相关maven依赖
<!-- 继承shiro关联包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.31</version> </dependency>
以上依赖全部配置完成之后,就该写配置文件了
其中配置文件分为ShiroConfig、MyRealm、还有一个CustomSessionManager用来自定义配置session
ShiroConfig
package com.fzf.web.config; import com.fzf.web.common.CustomSessionManager; import com.fzf.web.exception.MyExceptionHandler; import com.fzf.web.filter.CORSAuthenticationFilter; import com.fzf.web.realm.MyRealm; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.realm.Realm; import org.apache.shiro.session.mgt.SessionManager; 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.mgt.SecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.web.servlet.HandlerExceptionResolver; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { @Bean public Realm realm() { return new MyRealm(); } @Bean public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); //SecurityUtils.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //配置不会被拦截的链接,顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/sys/user/login", "anon"); //authc:所有url必须通过认证才能访问,anon:所有url都可以匿名访问 filterChainDefinitionMap.put("/**", "authc"); // filterChainDefinitionMap.put("/**", "corsAuthenticationFilter"); // shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap); shiroFilter.setLoginUrl("/unauth"); //自定义过滤器 Map<String, Filter> filterMap = new LinkedHashMap<>(); filterMap.put("corsAuthenticationFilter", corsAuthenticationFilter()); shiroFilter.setFilters(filterMap); return shiroFilter; } /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。 * @return */ @Bean public SimpleCookie rememberMeCookie(){ //System.out.println("ShiroConfiguration.rememberMeCookie()"); //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //<!-- 记住我cookie生效时间30天 ,单位秒;--> simpleCookie.setMaxAge(259200); return simpleCookie; } /** * cookie管理对象; * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中 * @return */ @Bean public CookieRememberMeManager rememberMeManager(){ //System.out.println("ShiroConfiguration.rememberMeManager()"); CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag==")); return cookieRememberMeManager; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager sm = new DefaultWebSecurityManager(); sm.setRealm(realm()); sm.setCacheManager(cacheManager()); //注入记住我管理器 sm.setRememberMeManager(rememberMeManager()); //注入自定义sessionManager sm.setSessionManager(sessionManager()); return sm; } //自定义sessionManager @Bean public SessionManager sessionManager() { return new CustomSessionManager(); } public CORSAuthenticationFilter corsAuthenticationFilter(){ return new CORSAuthenticationFilter(); } /** * Shiro生命周期处理器 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 注册全局异常处理 * @return */ @Bean(name = "exceptionHandler") public HandlerExceptionResolver handlerExceptionResolver() { return new MyExceptionHandler(); } }
CustomSessionManager
package com.fzf.web.common; import com.alibaba.druid.util.StringUtils; import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.Serializable; public class CustomSessionManager extends DefaultWebSessionManager { private static final Logger logger = LoggerFactory.getLogger(CustomSessionManager.class); private static final String AUTHORIZATION = "Authorization"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; public CustomSessionManager() { super(); setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);//如果请求头中有 Authorization 则其值为sessionId if (!StringUtils.isEmpty(sessionId)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return sessionId; } else { //否则按默认规则从cookie取sessionId return super.getSessionId(request, response); } } }
MyRealm
package com.fzf.web.realm; import com.fzf.web.mapper.SysPermissionMapper; import com.fzf.web.mapper.SysRoleMapper; import com.fzf.web.mapper.SysUserMapper; import com.fzf.web.model.SysPermission; import com.fzf.web.model.SysRole; import com.fzf.web.model.SysUser; import com.fzf.web.util.ValidateUtils; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import javax.annotation.Resource; import java.util.HashSet; import java.util.List; import java.util.Set; public class MyRealm extends AuthorizingRealm { @Resource private SysUserMapper sysUserMapper; @Resource private SysRoleMapper sysRoleMapper; @Resource private SysPermissionMapper sysPermissionMapper; /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = principalCollection.getPrimaryPrincipal().toString(); //根据用户名查询出用户记录 SysUser sysUser = sysUserMapper.selectByPrimaryKey(username); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<SysRole> roleList = sysRoleMapper.selectRolesByUsername(sysUser.getUsername()); Set<String> roles=new HashSet<String>(); if(roleList.size()>0){ for(SysRole role:roleList){ roles.add(role.getName()); //根据角色id查询所有资源 List<SysPermission> permissionList=sysPermissionMapper.selectByRoleId(role.getId()); for(SysPermission permission:permissionList){ if (ValidateUtils.isNotEmpty(permission.getFlag())){ info.addStringPermission(permission.getFlag()); // 添加权限 } } } } info.setRoles(roles); return info; } /** * 权限认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String username = authenticationToken.getPrincipal().toString(); SysUser sysUser = sysUserMapper.selectByPrimaryKey(username); if(sysUser!=null){ AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(sysUser.getUsername(),sysUser.getPassword(),"xxx"); return authcInfo; }else{ return null; } } }
其中MyRealm的doGetAuthorizationInfo方法可根据自己权限架构来自己写,因为有的项目不需要这么详细的操作,只要三张表就可以使用,具体可以根据自己的权限框架来写(多走几遍debug就能看懂doGetAuthorizationInfo知道怎么改了)