zoukankan      html  css  js  c++  java
  • 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 加入微信交流群
  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/dsxie/p/12818580.html
Copyright © 2011-2022 走看看