SpringBoot整合Shiro

作者: Grek | 来源:发表于2018-12-24 23:55 被阅读0次

    SpringBoot整合Shiro

    Apache Shiro

           Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。

    Apache Shiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。

    Shiro能做什么呢?

    1. 验证用户身份
    2. 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
    3. 在非 web 或 EJB 容器的环境下可以任意使用Session API
    4. 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
    5. 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
    6. 支持单点登录(SSO)功能
    7. 支持提供“Remember Me”服务,获取用户关联信息而无需登录

    快速上手

    先贴上pom.xml文件

    <dependencies>
            <!--自动配置支持、日志和YAML-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <!--JUnit、Hamcrest、Mockito-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--MyBatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
    
            <!-- shiro-spring -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.46</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    
            <!-- druid数据源驱动 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.9</version>
            </dependency>
        </dependencies>
    

    配置好自己的application.properties文件

    • 创建数据库

      • 我们在这里创建一个user表
      DROP TABLE IF EXISTS `user`;
      
      CREATE TABLE `user` (
        `id` int(11) NOT NULL AUTO_INCREMENT,
        `username` varchar(255) NOT NULL,
        `password` varchar(255) NOT NULL,
        `role` varchar(30) NOT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      LOCK TABLES `user` WRITE;
      /*!40000 ALTER TABLE `user` DISABLE KEYS */;
      
      INSERT INTO `user` (`id`, `username`, `password`, `role`)
      VALUES
          (1,'jmtang','1234','admin'),
          (2,'zhangsan','12345','user');
      
      /*!40000 ALTER TABLE `user` ENABLE KEYS */;
      UNLOCK TABLES;
      
    • 开始配置Shiro

      • 需要配置shiroconfig文件
        1. 根据ShiroConfig的配置,将相应的URL加载到Shiro框架中
        2. 用户执行登录时,会自动执行doGetAuthenticationInfo和doGetAuthorizationInfo方法进行认证和鉴权
        3. 用户进行访问操作,若无权限或者未登录,会根据shiroConfig的配置,自动跳转到相应页面
        4. 使用用户账户名和密码生成令牌---->执行登录(shiro本身并不知令牌是否合法,通过用户自行实现Realm进行比对,常用的是数据库查询)
          注意:这里面有个坑:SecurityManager导入的包应该来源与org.apache.shiro.mgt.SecurityManager
    @Configuration
    public class shiroconfig {
    
        /**
         *  主要配置一些相应的URL的规则和访问权限
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // 必须设置SecurityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            // setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
            shiroFilterFactoryBean.setLoginUrl("/notLogin");
            // 设置无权限时跳转的 url;
            shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
    
            // 设置拦截器
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            //游客,开发权限
            filterChainDefinitionMap.put("/guest/**", "anon");
            //用户,需要角色权限 “user”
            filterChainDefinitionMap.put("/user/**", "roles[user]");
            //管理员,需要角色权限 “admin”
            filterChainDefinitionMap.put("/admin/**", "roles[admin]");
            //开放登陆接口
            filterChainDefinitionMap.put("/login", "anon");
            //其余接口一律拦截
            //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
            filterChainDefinitionMap.put("/**", "authc");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            System.out.println("Shiro拦截器工厂类注入成功");
            return shiroFilterFactoryBean;
        }
    
    
        /**
         * 注入 securityManager
         */
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置realm.
            securityManager.setRealm(customRealm());
            return securityManager;
        }
    
        /**
         * 自定义身份认证 realm;
         * <p>
         * 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
         * 否则会影响 CustomRealm类 中其他类的依赖注入
         */
        @Bean
        public CustomRealm customRealm() {
            return new CustomRealm();
        }
    }
    

    Filter Chain说明

    1. 一个URL可以配置多个Filter,使用逗号分隔
    2. 当设置多个过滤器时,全部验证通过,才视为通过
    3. 部分过滤器可指定参数,如perms,roles

    Shiro内置的FilterChain,这里面举出几个常见的的FilterChain

    • anon: 所有url都都可以匿名访问
    • authc: 需要认证才能进行访问
    • roles: 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过
    • user: 配置记住我或认证通过可以访问
    • perms:参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算通过

    Shiro 开始实现两个方面

    • 登陆认证实现
    • 权限管理实现

    登陆认证实现

    1. 检查提交的进行认证的令牌信息
    2. 根据令牌信息从数据源(通常为数据库)中获取用户信息
    3. 对用户信息进行匹配验证。
    4. 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
    5. 验证失败则抛出AuthenticationException异常信息。
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("————身份认证方法————");
        // 获取用户的Token
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 根据Token中的username
        // 从数据库获取对应用户名密码的用户
        String password = userDao.getPassword(token.getUsername());
        if (null == password) {
            throw new AccountException("用户名不正确");
        } else if (!password.equals(new String((char[]) token.getCredentials()))) {
            throw new AccountException("密码不正确");
        }
        // 最终会封装到一个AuthenticationInfo类中
        return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
    }
    

    权限管理的实现

    • 获取身份验证信息
    • Shiro中 通过 Realm 来获取应用程序中的用户、角色及权限信息的。
    • 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("————权限认证————");
        // 获取用户的输入账号
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //获得该用户角色
        String role = userDao.getRole(username);
        Set<String> set = new HashSet<>();
        //需要将 role 封装到 Set 作为 info.setRoles() 的参数
        set.add(role);
        //设置该用户拥有的角色
        info.setRoles(set);
        return info;
    }
    

    接下来就是将URL权限分配给Controller
    主要有:

    • AdminController
    @RestController
    @RequestMapping("/admin")
    public class AdminController {
        @Autowired
        private ResultMap resultMap;
    
        @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
        public ResultMap getMessage() {
            return resultMap.success().message("您拥有管理员权限,可以获得该接口的信息!");
        }
    }
    
    • ExceptionContrller
    @RestControllerAdvice
    public class ExceptionController {
        private ResultMap resultMap;
    
        @Autowired
        public ExceptionController(ResultMap resultMap) {
            this.resultMap = resultMap;
        }
    
        // 捕捉 CustomRealm 抛出的异常
        @ExceptionHandler(AccountException.class)
        public ResultMap handleShiroException(Exception ex) {
            return resultMap.fail().message(ex.getMessage());
        }
    }
    
    • GuestController
    @RestController
    @RequestMapping("/guest")
    public class GuestController {
    
        @Autowired
        private ResultMap resultMap;
    
        @RequestMapping(value = "/enter", method = RequestMethod.GET)
        public ResultMap login() {
            return resultMap.success().message("欢迎进入,您的身份是游客");
        }
    
        @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
        public ResultMap submitLogin() {
            return resultMap.success().message("您拥有获得该接口的信息的权限!");
        }
    }
    
    • LoginController
    @RestController
    public class LoginController {
        @Autowired
        private ResultMap resultMap;
    
        @Autowired
        private UserDao userDao;
    
        @RequestMapping(value = "/notLogin", method = RequestMethod.GET)
        public ResultMap notLogin() {
            return resultMap.success().message("您尚未登陆!");
        }
    
        @RequestMapping(value = "/notRole", method = RequestMethod.GET)
        public ResultMap notRole() {
            return resultMap.success().message("您没有权限!");
        }
    
        @RequestMapping(value = "/logout", method = RequestMethod.GET)
        public ResultMap logout() {
            Subject subject = SecurityUtils.getSubject();
            //注销
            subject.logout();
            return resultMap.success().message("成功注销!");
        }
    
        /**
         * 登陆
         *
         * @param username 用户名
         * @param password 密码
         */
        @RequestMapping(value = "/login", method = RequestMethod.POST)
        public ResultMap login(String username, String password) {
            // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 在认证提交前准备 token(令牌)
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // 执行认证登陆
            subject.login(token);
            //根据权限,指定返回数据
            String role = userDao.getRole(username);
            if ("user".equals(role)) {
                return resultMap.success().message("欢迎登陆");
            }
            if ("admin".equals(role)) {
                return resultMap.success().message("欢迎来到管理员页面");
            }
            return resultMap.fail().message("权限错误!");
        }
    }
    
    • UserController
    @RestController
    @RequestMapping("/user")
    public class UserController{
        @Autowired
        private ResultMap resultMap;
    
        @RequestMapping(value = "/getMessage", method = RequestMethod.GET)
        public ResultMap getMessage() {
            return resultMap.success().message("您拥有用户权限,可以获得该接口的信息!");
        }
    }
    

    到此就可以用POSTMAN测试了,还没使用前后端分离连接起来,敬请期待!!!

    示例代码

    参考:

    相关文章

      网友评论

        本文标题:SpringBoot整合Shiro

        本文链接:https://www.haomeiwen.com/subject/uotxlqtx.html