• 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

  • 相关阅读:
    centos安装MySQL5.7
    centos搭建ftp服务器的方法
    centos 7 卸载 mariadb 的正确命令
    MySQL5.7关于密码二三事
    第四次:渗透练习,xss学习
    第三次靶场练习:通过抓包,绕过内部限制
    第二次靶场练习:cookie注入
    第一次靶场练习:SQL注入(1)
    Linux用户和组管理
    Linux基础命令(三)
  • 原文地址:https://www.cnblogs.com/zhaiyt/p/10335963.html
Copyright © 2020-2023  润新知