zoukankan      html  css  js  c++  java
  • 从零开始使用spring boot搭建前后端分离框架(二、集成shiro)

    上一章已经完整的搭建好了一个框架,现在需要集成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知道怎么改了)

  • 相关阅读:
    Cipherlab CPT9300手持扫描枪开发体验 [转]
    引用(ajaxfileupload.js) ajaxfileupload.js报jQuery.handleError is not a function错误解决方法
    C#锐利体验2.0:泛型编程
    Visual C#中调用Windows服务初探
    C#操作XML代码整理
    个人代码库のC#背景色渐变的功能
    ~~ C#数字时钟 ~~
    DevExpress随笔10.1.5的汉化与破解
    用C#获取局域网内所有机器
    C# 图片格式(JPG,BMP,PNG,GIF)等转换为ICO图标
  • 原文地址:https://www.cnblogs.com/fuhui-study-footprint/p/11661398.html
Copyright © 2011-2022 走看看