zoukankan      html  css  js  c++  java
  • spring-boot+mybatisPlus+shiro的集成demo 我用了5天

    spring-boot + mybatis-plus + shiro 的集成demo我用了五天

      关于shiro框架,我还是从飞机哪里听来的,就连小贱都知道,可我母鸡啊。简单百度了下,结论很好上手,比spring的security要简单许多...于是我就是开始了我的shiro学习之路 。正巧这几天在研究spring-boot集成mybatis-plus 于是乎我就把shiro也揉了进去,但是效果并不像我预期想象的那样。

    以下是我这几天血泪换来的成果-->

    基本概念

    shiro :隶属Apache 简单易用的java安全框架 三大件 :Subject, SecurityManager 和 Realm
    Subject:即当前操作用户
    SecurityManager:安全管理器
    Realm:用户数据的概念,域

    过程:

    1)建表 数据库一般至少有五张表

    1-user:用户账号密码

    2-role:角色ID,一个账户可以有很多角色

    3-permission权限ID,一个角色可以有很多权限

    4-user_role关系对照表:记录每个userID有的角色

    5-role_permission关系对照表,记录每个role有的permission

    -- 用户表
    DROP TABLE IF EXISTS `user_info`;
    CREATE TABLE `user_info` (
    `uid` int(11) NOT NULL,
    `name` varchar(255) DEFAULT NULL,
    `pass_word` varchar(255) DEFAULT NULL COMMENT '密码',
    `salt` varchar(255) DEFAULT NULL COMMENT '加密',
    `state` tinyint(4) NOT NULL COMMENT '状态',
    `username` varchar(255) DEFAULT NULL COMMENT '用户名',
    `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
    `crtime` datetime DEFAULT NULL COMMENT '创建时间',
    PRIMARY KEY (`uid`),
    UNIQUE KEY(`username`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- 角色表
    DROP TABLE IF EXISTS `sys_role`;
    CREATE TABLE `sys_role` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `available` bit(1) DEFAULT NULL,
    `description` varchar(255) DEFAULT NULL,
    `role` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    -- 用户角色表
    DROP TABLE IF EXISTS `sys_user_role`;
    CREATE TABLE `sys_user_role` (
    `uid` int(11) NOT NULL,
    `role_id` int(11) NOT NULL,
     PRIMARY KEY(`uid`,`role_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- 权限表
    DROP TABLE IF EXISTS `sys_permission`;
    CREATE TABLE `sys_permission` (
    `id` int(11) NOT NULL COMMENT 'PMK',
    `available` bit(1) DEFAULT NULL COMMENT '是否激活',
    `name` varchar(255) DEFAULT NULL,
    `parent_id` bigint(20) DEFAULT NULL,
    `parent_ids` varchar(255) DEFAULT NULL,
    `permission` varchar(255) DEFAULT NULL COMMENT '权限',
    `resource_type` enum('menu','button') DEFAULT NULL,
    `url` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- 角色权限表
    DROP TABLE IF EXISTS `sys_role_permission`;
    CREATE TABLE `sys_role_permission` (
    `permission_id` int(11) NOT NULL,
    `role_id` int(11) NOT NULL,
     PRIMARY KEY(`role_id`,`permission_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    2)项目搭建

    spring-boot mybatis-plus shiro 

    1.pom.xml 贴出部分

    <!--             mybits-plus            -starter-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatisplus-spring-boot-starter</artifactId>
                <version>1.0.5</version>
            </dependency>
            <!-- MP 核心库 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus</artifactId>
                <version>2.1.8</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>
            <!-- 模板引擎 代码生成 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity</artifactId>
                <version>1.7</version>
            </dependency>
            <!--              mybits-plus                -end-->
            <!--shiro 登录认证-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>5.1.35</scope>
            </dependency>
            <!--druid 数据库连接池监控-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.0</version>
            </dependency>
            <!--jasypt 数据库加解密-->
            <dependency>
                <groupId>com.github.ulisesbocchio</groupId>
                <artifactId>jasypt-spring-boot-starter</artifactId>
                <version>1.8</version>
            </dependency>

    2.先集成mybatis-plus 利用自动生成方法生成 新建5张表的POJO 和 Mapper Service文件


    3.编写ShiroConfig配置类

    package com.zxt.ms.configs;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    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.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * @ClassName ShiroConfig
     * @Description ms 梦想家
     * @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
     * @Date 2019/1/26 17:27
     * @Version 1.0
     */
    @Slf4j
    @Configuration
    public class ShiroConfig {
    
        @Bean(name = "lifecycleBeanPostProcessor")
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        /**
         * shiro自带过滤器,无需再另外设置filter
         *
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
    
            log.info("ShiroConfig.shirFilter() start ...");
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            //设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //配置过滤器
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    
            //没登录的页面
            shiroFilterFactoryBean.setLoginUrl("/notLogin");
            // 设置无权限时跳转的 url;
            shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
    
            /*
             * shiro 内置枚举
             * anon 表示可以匿名使用
             * authc 表示需要认证(登录)才能使用,没有参数
             */
            //静态资源允许访问
            filterChainDefinitionMap.put("/css/**", "anon");
            filterChainDefinitionMap.put("/images/**", "anon");
            filterChainDefinitionMap.put("/js/**", "anon");
            filterChainDefinitionMap.put("/layer/**", "anon");
    
            //游客,开发权限
            filterChainDefinitionMap.put("/guest/**", "anon");
            //用户,需要角色权限 “user”
            filterChainDefinitionMap.put("/user/**", "roles[user]");
            //管理员,需要角色权限 “admin”
            filterChainDefinitionMap.put("/admin/**", "roles[admin]");
            //开放登陆接口
            filterChainDefinitionMap.put("/login", "anon");
            filterChainDefinitionMap.put("/loginUser", "anon");
            //其余接口一律拦截
            //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
            filterChainDefinitionMap.put("/**", "authc");
    
            //过滤器注入工厂类
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
            log.info("ShiroConfig.shirFilter() end ...");
            return shiroFilterFactoryBean;
        }
    
        /**
         * @return org.apache.shiro.mgt.SecurityManager
         * @Description <安全管理器Bean>
         * @Author Zhaiyt
         * @Date 14:35 2019/1/28
         * @Param
         **/
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //注入realm
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        /**
         * @return com.zxt.ms.configs.ShiroRealm
         * @Description <域 的概念 Shiro 从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,
         * 那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;
         * 可以把Realm看成DataSource , 即安全数据源>
         * @Author Zhaiyt
         * @Date 14:38 2019/1/28
         * @Param
         **/
        @Bean
        public ShiroRealm myShiroRealm() {
            ShiroRealm myShiroRealm = new ShiroRealm();
            myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myShiroRealm;
        }
    
        /**
         * @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
         * @Description <异常处理>
         * @Author Zhaiyt
         * @Date 15:20 2019/1/28
         * @Param
         **/
        @Bean(name = "simpleMappingExceptionResolver")
        public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
            SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
            Properties mappings = new Properties();
            mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
            mappings.setProperty("UnauthorizedException", "403");
            exceptionResolver.setExceptionMappings(mappings);  // None by default
            exceptionResolver.setDefaultErrorView("error");    // No default
            exceptionResolver.setExceptionAttribute("ex");     // Default is "exception"
            return exceptionResolver;
        }
        
        /**
         * 因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。
         * @Description <加密>
         * @Author Zhaiyt
         * @Date 16:18 2019/1/29
         * @Param 
         * @return org.apache.shiro.authc.credential.HashedCredentialsMatcher
         **/
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
            return hashedCredentialsMatcher;
        }
    
        /**
         * @Description <因为只有开启了AOP才执行doGetAuthorizationInfo(),也就权限拦截>
         * @Author Zhaiyt
         * @Date 16:18 2019/1/29
         * @Param 
         * @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
         **/
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }

    shiro配置类编写主要需要注意以下几点:
      1.Shiro 过滤器
      2.SecurityManager 安全管理器
      3.加密方式
      4.域需要注入到SecurityManager中

    4.自定义ShiroRealm编写:

    package com.zxt.ms.configs;
    
    import com.zxt.ms.entity.UserInfo;
    import com.zxt.ms.service.IUserInfoService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.crypto.hash.SimpleHash;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.Set;
    
    /**
     * @ClassName ShiroRealm
     * @Description ms 梦想家
     * @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
     * @Date 2019/1/26 16:54
     * @Version 1.0
     */
    @Slf4j
    @Component
    public class ShiroRealm extends AuthorizingRealm {
    
    
        @Autowired
        private IUserInfoService userInfoServiceImpl;
    
        /**
         * @Description <权限验证>
         * @Author Zhaiyt
         * @Date 14:57 2019/1/28
         * @Param
         * @return org.apache.shiro.authz.AuthorizationInfo
         **/
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            //因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
            if (!SecurityUtils.getSubject().isAuthenticated()) {
                log.info("非正常退出,清除缓存");
                doClearCache(principalCollection);
                SecurityUtils.getSubject().logout();
                return null;
            }
    
            UserInfo userInfo = (UserInfo)principalCollection.getPrimaryPrincipal();
            String username = userInfo.getUsername();
            //用户存在 授权
            if(StringUtils.isNotBlank(username)){
                SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
                Set<String> roles=userInfoServiceImpl.findRoleByUser(userInfo.getUsername());
                Set<String> permissions=userInfoServiceImpl.findPermissionByUser(userInfo.getUsername());
                authorizationInfo.setRoles(roles);
                authorizationInfo.setStringPermissions(permissions);
                return authorizationInfo;
            }
            return null;
        }
    
        /**
         * @Description <身份验证>
         * @Author Zhaiyt
         * @Date 14:58 2019/1/28
         * @Param
         * @return org.apache.shiro.authc.AuthenticationInfo
         **/
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            log.info("shiroRealm.doGetAuthenticationInfo() start ...");
            //获取用户的输入的账号.
            String username = (String)authenticationToken.getPrincipal();
    
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            if(StringUtils.isNotBlank(username)){
    
                UserInfo userInfo = userInfoServiceImpl.findByUsername(username);
    
                if(userInfo == null){
                    log.error("用户不存在");
                    throw new UnknownAccountException("用户名或密码错误!");
                }
    
                SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                        userInfo, //用户名
                        userInfo.getPassword(), //密码
                        ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
                        getName()  //realm name
                );
                return authenticationInfo;
            }
            throw new UnknownAccountException("用户名或密码错误!");
        }
        @Bean
        public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
            creator.setProxyTargetClass(true);
            return creator;
        }
    
        /**
         * 清除权限缓存
         * 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
         * 然后调用其clearCache方法。
         */
        public void clearCache() {
            PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
            super.clearCache(principals);
        }
    
        public static void main(String[] args) {
            String hashAlgorithmName = "MD5";
            String credentials = "123456";
            int hashIterations = 2;
            ByteSource credentialsSalt = ByteSource.Util.bytes("zhaizhai");
            Object obj = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations);
            System.out.println(obj);
        }
    }

    ShiroRealm 需要集成 AuthorizingRealm 这个里面要实现两个方法,一个认证 doGetAuthenticationInfo,一个授权 doGetAuthorizationInfo

    5.测试controller编写

    package com.zxt.ms.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections4.MapUtils;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ClassName TestController
     * @Description ms 梦想家
     * @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
     * @Date 2019/1/28 16:16
     * @Version 1.0
     */
    @Slf4j
    @Controller
    public class HomeController {
    
        @RequiresPermissions(value = "admin")
        @RequestMapping(value = "/index")
        public String test(){
            return "index";
        }
    
        @GetMapping(value = "/login")
        public String login(){
            return "login";
        }
    
        @RequestMapping("/loginUser")
        @ResponseBody
        public Map<String,Object> login(HttpServletRequest request, HttpServletResponse response) throws Exception{
            log.info("HomeController.login()");
            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String rememberMe = request.getParameter("rememberMe");
            Map<String,Object> map = new HashMap<>();
            String ret="";
            Subject currentUser = SecurityUtils.getSubject();
            if (!currentUser.isAuthenticated()) {
                UsernamePasswordToken token = new UsernamePasswordToken(username,
                        password);
                token.setRememberMe(rememberMe=="true");
                map.put("code","FAILD");
                try {
                    currentUser.login(token);
                    map.put("code","SUCCESS");
                    map.put("msg","登陆成功");
                } catch (UnknownAccountException ex) {
                    map.put("msg","账号错误");
                    log.error(MapUtils.getString(map,"msg"));
                } catch (IncorrectCredentialsException ex) {
                    map.put("msg","密码错误");
                    log.error(MapUtils.getString(map,"msg"));
                } catch (LockedAccountException ex) {
                    map.put("msg","账号已被锁定,请与管理员联系");
                    log.error(MapUtils.getString(map,"msg"));
                } catch (AuthenticationException ex) {
                    map.put("msg","您没有授权");
                    log.error(MapUtils.getString(map,"msg"));
                }
            }
            return map;
        }
    }

    代码基本上就上面那些,但是我深知,仅仅只有上面那些东西根本就跑不起来,我不知道我为什么要写这样的东西,可能某个小哥在看我写的博客时候也会骂我吧,写的什么鬼鸡仔。。。 我是没有办法把所有东西都整到这来,没有任何意义,有些坑必须自己去踩,只有踩过了,也许才会记得更清楚。

    下面我要说坑了:

    1.编写shiroFilter的时候我没有将静态数据过滤,导致页面展示格式错乱

    2.登陆使用的是ajax,而shiro当做是表单的提交,所以一开始的 login controller 写的是有问题,导致一直报 302

    3.认证无法通过,在shiro配置类中我指定了加密方式,为MD5 2次离散 ,在Realm中我指定了需要加 salt 的加密方式,因此密码的加密方式为  MD5 + salt 我使用main 跑出来加密后的数据,添加至数据库,可是一直无法认证成功,后发现在SecurityManager注入的ShiroRealm实体没有set加密方法...

    4.认证成功后无法回调授权方法,原因,需要权限校验的方法上添加 @RequiresPermissions(value = "admin") OK

  • 相关阅读:
    668. Kth Smallest Number in Multiplication Table
    658. Find K Closest Elements
    483. Smallest Good Base
    475. Heaters
    454. 4Sum II
    441. Arranging Coins
    436. Find Right Interval
    410. Split Array Largest Sum
    392. Is Subsequence
    378. Kth Smallest Element in a Sorted Matrix
  • 原文地址:https://www.cnblogs.com/zhaiyt/p/10335963.html
Copyright © 2011-2022 走看看