• Spring Boot集成Shrio实现权限管理


    Spring Boot集成Shrio实现权限管理
     
     
    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。相比于Spring Security,功能没有那么强大,但现实开发中,我们也不需要那么多的功能。
     
    shiro中三个核心组件:Subject, SecurityManager 和 Realms
    • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
    • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
    • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。用户一般会自定义Ream,集成AuthorizingRealm。
     
    对于shiro的基本概念介绍如上,本文主要讲Spring Boot如何集成shiro,如何使用。另外该项目使用mybatis-plus操纵数据库,如果有朋友不知道mybatis-plus如何使用,点击链接https://mp.baomidou.com/ 查看如何而是用。项目中pom.xml文件内容如下
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.6.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com</groupId>
        <artifactId>springboot-shrio</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springboot-shrio</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.13</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
            <!--json-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.5</version>
            </dependency>
            <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>3.3.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>2.2</version>
            </dependency>
            <dependency>
                <groupId>org.freemarker</groupId>
                <artifactId>freemarker</artifactId>
                <version>2.3.30</version>
            </dependency>
            <dependency>
                <groupId>com.ibeetl</groupId>
                <artifactId>beetl</artifactId>
                <version>3.1.3.RELEASE</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
     
    在项目中有两个至关重要类需要我们自定义实现,一个是shiroConfig类,一个是CustonRealm类。
    ShiroConfig类:
    顾名思义就是对shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。

    package com.shiro.config;
    
    import com.shiro.realm.CustomRealm;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author IT咸鱼
     * @Date 2020/04/26
     */
    @Configuration
    public class ShiroConfig {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
        //不加这个注解不生效,具体不详
        @Bean
        @ConditionalOnMissingBean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
            defaultAAP.setProxyTargetClass(true);
            return defaultAAP;
        }
    
        //将自己的验证方式加入容器
        @Bean
        public CustomRealm myShiroRealm() {
            CustomRealm customRealm = new CustomRealm();
            return customRealm;
        }
    
        //权限管理,配置主要是Realm的管理认证
        @Bean
        public SecurityManager securityManager() {
            logger.info("SecurityManager注册完成");
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            logger.info("设置对应的过滤条件和跳转条件");
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            Map<String,String> map = new HashMap<String,String>();
            // 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录
            map.put("/css/**", "anon");
            map.put("/fonts/**", "anon");
            map.put("/img/**", "anon");
            map.put("/js/**", "anon");
            map.put("/html/**", "anon");
            //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
            map.put("/logout", "logout");
            //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
            //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
            map.put("/**", "authc");
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
    
            //未授权界面;
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        /**
         *  开启shiro aop注解支持.
         *  使用代理方式;所以需要开启代码支持;
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
     
    CustomRealm类:
    自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。

    package com.shiro.realm;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.shiro.entity.*;
    import com.shiro.service.ITPermissionService;
    import com.shiro.service.ITRoleService;
    import com.shiro.service.ITUserService;
    import com.shiro.service.LoginService;
    import org.apache.catalina.authenticator.jaspic.SimpleAuthConfigProvider;
    import org.apache.shiro.authc.*;
    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 org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.List;
    
    public class CustomRealm extends AuthorizingRealm {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        LoginService loginServiceImpl;
    
        @Autowired
        ITUserService tUserServiceImpl;
    
        @Autowired
        ITRoleService tRoleServiceImpl;
    
        @Autowired
        ITPermissionService tPermissionServiceImpl;
    
        /**
         * 授权
         * @param principalCollection
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) throws AuthenticationException {
            logger.info("CustomRealm.doGetAuthorizationInfo,PrincipalCollection={}", principalCollection);
            TUser tUser = (TUser)principalCollection.getPrimaryPrincipal();
            //添加角色和权限
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            List<TRole> roles = tRoleServiceImpl.getRoleByUserId(tUser.getId());
            for (TRole tRole : roles){
                authorizationInfo.addRole(tRole.getRoleCode());
                List<TPermission> permissions = tPermissionServiceImpl.getPermissionsByRoleId(tRole.getId());
                for (TPermission tPermission : permissions){
                    authorizationInfo.addStringPermission(tPermission.getPermissionCode());
                }
            }
            return authorizationInfo;
        }
    
        /**
         * 用户调用登录接口时调用该方法,校验用户合法性
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            logger.info("CustomRealm.doGetAuthenticationInfo,AuthenticationToken={}", authenticationToken);
            if (authenticationToken.getPrincipal() == null){
                return null;
            }
            String userName = authenticationToken.getPrincipal().toString();
            QueryWrapper<TUser> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(TUser::getUserName, userName);
            TUser tUser = tUserServiceImpl.getOne(queryWrapper);
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            if (tUser == null){
                throw new UnknownAccountException();
            }
            if (tUser.getStatus() == 0){
                throw new LockedAccountException();
            }
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(tUser, tUser.getPassword().toString(), getName());
            return simpleAuthenticationInfo;
        }
    }
     
    创建LoginController类,使用postman测试登录接口,获取权限

    package com.shiro.controller;
    
    import com.shiro.dto.LoginDto;
    import com.shiro.entity.TUser;
    import com.shiro.entity.User;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationException;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.Subject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.Serializable;
    import java.util.Deque;
    
    
    @RestController
    public class LoginController {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @RequestMapping("/login")
        public String login(LoginDto loginDto) {
            logger.info("/login, LoginDto={}", loginDto);
            //添加用户认证信息
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    loginDto.getUserName(),
                    loginDto.getPassword()
            );
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
            } catch (AuthenticationException e) {
                e.printStackTrace();
                return "账号或密码错误!";
            } catch (AuthorizationException e) {
                e.printStackTrace();
                return "没有权限";
            }
            return "login success";
        }
    
        @RequestMapping("/logout")
        public String logout(){
            logger.info("/logout");
            Subject subject = SecurityUtils.getSubject();
            if(null!=subject){
                String username = ((TUser) SecurityUtils.getSubject().getPrincipal()).getUserName();
                logger.info("username={}", username);
    
            }
            return "logout success";
        }
    }
    使用postman访问/login接口
     
    登录成功后,根据登录成功后的用户权限去操作接口,demo中只有admin和common角色,admin可以增加、删除、更新、读取,common用户只能读取,拿用户管理类TUserController作为例子讲解
    package com.shiro.controller;
    
    
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.shiro.common.ResultHandler;
    import com.shiro.entity.TUser;
    import com.shiro.service.ITUserService;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    /**
     * <p>
     *  前端控制器
     * </p>
     *
     * @author xieya
     * @since 2020-04-28
     */
    @RestController
    @RequestMapping("/user")
    public class TUserController {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private ITUserService tUserServiceImpl;
    
        @RequiresRoles("admin")//指定需要有admin角色
        @RequiresPermissions({"create","update"})//需要有create、update权限
        @PostMapping("/save-or-update")
        public String saveOrUpdate(@RequestBody TUser tUser){
            logger.info("/save-or-update, TUser={}", tUser);
            JSONObject jsonObject = null;
            try {
                jsonObject = new JSONObject();
                if (tUser == null){
                    return ResultHandler.handler(jsonObject, "-1001", "param null");
                }
                tUserServiceImpl.saveOrUpdate(tUser);
                ResultHandler.handler(jsonObject, "0", "Success");
            } catch (Exception e) {
                logger.info("System Exception,e={}", e);
                return ResultHandler.handler(jsonObject, "-9001", "System Exception");
            }
            return jsonObject.toString();
        }
    
        @RequiresRoles("admin")
        @RequiresPermissions("delete")
        @GetMapping("/delete")
        public String delete(Long id){
            logger.info("/delete, id={}", id);
            JSONObject jsonObject = null;
            try {
                jsonObject = new JSONObject();
                if (id == null){
                    return ResultHandler.handler(jsonObject, "-1001", "param null");
                }
                tUserServiceImpl.removeById(id);
                ResultHandler.handler(jsonObject, "0", "Success");
            } catch (Exception e) {
                logger.info("System Exception,e={}", e);
                return ResultHandler.handler(jsonObject, "-9001", "System Exception");
            }
            return jsonObject.toString();
        }
    
        @PostMapping("/retrieve")
        public String retrieve(@RequestBody TUser tUser){
            logger.info("/retrieve, TUser={}", tUser);
            JSONObject jsonObject = null;
            try {
                jsonObject = new JSONObject();
                if (tUser == null){
                    return ResultHandler.handler(jsonObject, "-1001", "param null");
                }
                QueryWrapper<TUser> queryWrapper = new QueryWrapper<>();
                if (tUser.getId() != null){
                    queryWrapper.lambda().eq(TUser::getId, tUser.getId());
                }
                if (!StringUtils.isEmpty(tUser.getUserName())){
                    queryWrapper.lambda().eq(TUser::getUserName, tUser.getUserName());
                }
                List<TUser> list = tUserServiceImpl.list(queryWrapper);
                ResultHandler.handler(jsonObject, "0", "Success", list);
            } catch (Exception e) {
                logger.info("System Exception,e={}", e);
                return ResultHandler.handler(jsonObject, "-9001", "System Exception");
            }
            return jsonObject.toString();
        }
    }

     登录之后使用postman去访问“/user/save-or-update”接口

    如果在没有登录的情况下访问该接口,就会出现如下错误,在没有登录的请况下,shiro会自动的将接口访问重置到login接口login3

     一个简单的小项目,希望能帮上大家

    技术交流QQ群:579949017 或者添加个人微信:xieya0126 加入微信交流群
  • 相关阅读:
    sql 修改表名、列名、列类型
    .Net WinForm下配置Log4Net(总结不输出原因)
    ubuntu20.04 搭建门罗币节点
    python2 和 python3里StringIO和BytesIO的区别
    java.lang.IllegalArgumentException: java.lang.ClassCastException
    iphoneX安全边界
    ios中禁用回弹效果
    将nodejs回调方法变为promise
    实现trim方法
    flex实现三个div上中下布局
  • 原文地址:https://www.cnblogs.com/dsxie/p/12818580.html
Copyright © 2020-2023  润新知