• springboot整合shiro全流程


    转载自
    王诗林-springboot整合shiro(完整版)
    南风-超详细 Spring Boot 整合 Shiro 教程!
    SpringBoot集成Shiro极简教程(实战版)

    1. Shiro是什么?

    Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。

    它的主要作用是用来做身份认证、授权、会话管理和加密等操作。

    什么意思?大白话就是判断用户是否登录、是否拥有某些操作的权限等。

    其实不用 Shiro,我们使用原生 Java API 就可以完成安全管理,很简单,使用过滤器去拦截用户的各种请求,然后判断是否登录、是否拥有某些权限即可。

    我们完全可以完成这些操作,但是对于一个大型的系统,分散去管理编写这些过滤器的逻辑会比较麻烦,不成体系,所以需要使用结构化、工程化、系统化的解决方案。

    任何一个业务逻辑,一旦上升到企业级的体量,就必须考虑使用系统化的解决方案,也就是框架,否则后期的开发成本是相当巨大的,Shiro 就是来解决安全管理的系统化框架。

    2. Shiro 核心组件

    1、UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息创建令牌 Token,登录的过程即 Shiro 验证令牌是否具有合法身份以及相关权限。

    2、 SecurityManager,Shiro 的核心部分,负责安全认证与授权。

    3、Subject,Shiro 的一个抽象概念,包含了用户信息。

    4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现。

    5、AuthenticationInfo,用户的角色信息集合,认证时使用。

    6、AuthorizationInfo,角色的权限信息集合,授权时使用。

    7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。

    8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建一个个 Filter 对象来完成。

    Shiro 的运行机制如下图所示:

    3. 具体实现

    3.1 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.6.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.rogn</groupId>
        <artifactId>mybp</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>mybp</name>
        <description>mybp</description>
        <properties>
            <java.version>18</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
                <scope>provided</scope>
            </dependency>
    
            <!-- shiro -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.9.0</version>
            </dependency>
    
            <!--热部署依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    3.2 user.java(用户实体类):

    package com.rogn.mybp.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Set;
    
    @Data
    @NoArgsConstructor   // 无参构造函数
    @AllArgsConstructor  // 有参构造函数
    public class User {
        private Integer id;
    
        private String username;
        private String password;
    
        /**
         * 用户对应的角色集合
         */
        private Set<Role> roles;
    }
    

    3.3 Role.java(角色对应实体类):

    package com.rogn.mybp.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    
    import java.util.Set;
    
    @Data
    @AllArgsConstructor
    public class Role {
        private Integer id;
        private String roleName;
        /**
         * 角色对应权限集合
         */
        private Set<Permissions> permissions;
    }
    

    3.4 Permissions.java(权限对应实体类):

    package com.rogn.mybp.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    
    @Data
    @AllArgsConstructor
    public class Permissions {
        private Integer id;
        private String permissionsName;
    }
    

    3.5 LoginService.java

    package com.rogn.mybp.service;
    
    import com.rogn.mybp.entity.User;
    import org.springframework.stereotype.Service;
    
    
    public interface LoginService {
        User getUserByName(String userName);
    }
    

    3.6 LoginServiceImpl.java

    package com.rogn.mybp.service.impl;
    
    import com.rogn.mybp.entity.Permissions;
    import com.rogn.mybp.entity.Role;
    import com.rogn.mybp.entity.User;
    import com.rogn.mybp.service.LoginService;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    @Service
    public class LoginServiceImpl implements LoginService {
        @Override
        public User getUserByName(String getMapByName) {
            return getMapByName(getMapByName);
        }
    
        /**
         * 模拟数据库查询
         *
         * @param userName 用户名
         * @return User
         */
        public User getMapByName(String userName) {
            Permissions permissions1 = new Permissions(1, "query");
            Permissions permissions2 = new Permissions(2, "add");
            Set<Permissions> permissionsSet = new HashSet<>();
            permissionsSet.add(permissions1);
            permissionsSet.add(permissions2);
    
            Role role = new Role(1, "admin", permissionsSet);
            Set<Role> roleSet = new HashSet<>();
            roleSet.add(role);
    
            User user = new User(1, "wsl", "123456", roleSet);
            Map<String, User> map = new HashMap<>();
            map.put(user.getUsername(), user);
    
            Set<Permissions> permissionsSet1 = new HashSet<>();
            permissionsSet1.add(permissions1);
            Role role1 = new Role(2, "user", permissionsSet1);
            Set<Role> roleSet1 = new HashSet<>();
            roleSet1.add(role1);
            User user1 = new User(2, "zhangsan", "123456", roleSet1);
            map.put(user1.getUsername(), user1);
            return map.get(userName);
        }
    }
    

    3.7 CustomRealm.java:

    自定义Realm用于查询用户的角色和权限信息并保存到权限管理器

    对 URL 进行拦截,没有认证的需要认证,认证成功的则可以根据需要判断角色及权限。
    这个过滤器需要开发者自定义,然后去指定认证和授权的逻辑,继承抽象类 AuthorizingRealm,实现两个抽象方法分别完成授权和认证的逻辑。

    package com.rogn.mybp.shiro;
    
    import com.rogn.mybp.entity.Permissions;
    import com.rogn.mybp.entity.Role;
    import com.rogn.mybp.entity.User;
    import com.rogn.mybp.service.LoginService;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    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.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.StringUtils;
    
    public class CustomRealm extends AuthorizingRealm {
    
        @Autowired
        private LoginService loginService;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取登录用户名
            String name = (String) principalCollection.getPrimaryPrincipal();
            //查询用户名称
            User user = loginService.getUserByName(name);
            //添加角色和权限
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            for (Role role : user.getRoles()) {
                //添加角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                //添加权限
                for (Permissions permissions : role.getPermissions()) {
                    simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
                }
            }
            return simpleAuthorizationInfo;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
                return null;
            }
            //获取用户信息
            String name = authenticationToken.getPrincipal().toString();
            User user = loginService.getUserByName(name);
            if (user == null) {
                //这里返回后会报出对应异常
                return null;
            } else {
                //这里验证authenticationToken和simpleAuthenticationInfo的信息
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
                return simpleAuthenticationInfo;
            }
        }
    }
    

    3.8 ShiroConfig.java:

    把CustomRealm和SecurityManager等注入到spring容器中,这个配置类中一共自动装配了 5 个 bean 实例

    • CustomRealm:自定义过滤器 MyRealm,我们的业务逻辑全部定义在这个 bean 中
    • DefaultWebSecurityManager:将 MyRealm 注入到 DefaultWebSecurityManager bean 中,完成注册
    • ShiroFilterFactoryBean:这是 Shiro 自带的一个 Filter 工厂实例,所有的认证和授权判断都是由这个 bean 生成的 Filter 对象来完成的,这就是 Shiro 框架的运行机制,开发者只需要定义规则,进行配置,具体的执行者全部由 Shiro 自己创建的 Filter 来完成。

    自定义过滤器创建完成之后,需要进行配置才能生效,在 Spring Boot 应用中,不需要任何的 XML 配置,直接通过配置类进行装配,也就是@Configuration

    所以我们需要给 ShiroFilterFactoryBean 实例注入认证及授权规则,如下所示。

    认证过滤器:

    • anon:无需认证即可访问,游客身份。
    • authc:必须认证(登录)才能访问。
    • authcBasic:需要通过 httpBasic 认证。
    • user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。

    授权过滤器:

    • perms:必须拥有对某个资源的访问权限(授权)才能访问。
    • role:必须拥有某个角色权限才能访问。
    • port:请求的端口必须为指定值才可以访问。
    • rest:请求必须是 RESTful,method 为 post、get、delete、put。
    • ssl:必须是安全的 URL 请求,协议为 HTTPS。
    package com.rogn.mybp.config;
    
    import com.rogn.mybp.shiro.CustomRealm;
    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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.apache.shiro.mgt.SecurityManager;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfig {
        @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() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, String> map = new HashMap<>();
            //登出
            map.put("/logout", "logout");    // 允许匿名访问
            //对所有用户认证
            map.put("/**", "authc");   // 进行身份认证后才能访问
            //登录
            shiroFilterFactoryBean.setLoginUrl("/login");
            //首页
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //错误页面,认证不通过跳转
            shiroFilterFactoryBean.setUnauthorizedUrl("/error");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    

    3.9 LoginController.java

    我们编写一个简单的登录方法,对应不同的角色或权限拦截,一个admin方法(需要admin role),一个add方法(需要add permision),一个query方法(需要query permission)

    package com.rogn.mybp.controller;
    
    import com.rogn.mybp.entity.User;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UnknownAccountException;
    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.subject.Subject;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Slf4j
    public class LoginController {
        @GetMapping("/login")
        public String login(User user) {
            if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {
                return "请输入用户名和密码!";
            }
            //用户认证信息
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    user.getUsername(),
                    user.getPassword()
            );
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
    //            subject.checkRole("admin");
    //            subject.checkPermissions("query", "add");
            } catch (UnknownAccountException e) {
                log.error("用户名不存在!", e);
                return "用户名不存在!";
            } catch (AuthenticationException e) {
                log.error("账号或密码错误!", e);
                return "账号或密码错误!";
            } catch (AuthorizationException e) {
                log.error("没有权限!", e);
                return "没有权限";
            }
            return "login success";
        }
    
        @RequiresRoles("admin")
        @GetMapping("/admin")
        public String admin() {
            return "admin success!";
        }
    
        @RequiresPermissions("query")
        @GetMapping("/query")
        public String query() {
            return "query success!";
        }
    
        @RequiresPermissions("add")
        @GetMapping("/add")
        public String add() {
            return "add success!";
        }
    }
    

    注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下

    3.10 MyExceptionHandler.java

    package com.rogn.mybp.common;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.authz.AuthorizationException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @ControllerAdvice
    @Slf4j
    public class MyExceptionHandler {
        @ExceptionHandler
        @ResponseBody
        public String ErrorHandler(AuthorizationException e) {
            log.error("没有通过权限验证!", e);
            return "没有通过权限验证!";
        }
    }
    

    完整的代码可见

    4. 测试

    共有两个用户:wsl和zhangsan,两种用户:admin和user,两种权限:query和add

                /  query
    wsl--admin
                \  add
     
    zhangsan--user--query
    
    

    因此,对于wsl:
    http://localhost:8080/login?username=wsl&password=123456 "login success"
    http://localhost:8080/admin "admin success!"
    http://localhost:8080/query "query success!"
    http://localhost:8080/add "add success!"

    对于zhangsan:
    http://localhost:8080/login?username=zhangsan&password=123456 "login success"
    http://localhost:8080/admin "没有通过权限验证!"
    http://localhost:8080/query "query success!"
    http://localhost:8080/add "没有通过权限验证!"

  • 相关阅读:
    2.HTML案例二 头条页面
    1.HTML入门
    33.1.网络编程入门
    32.原子性
    【转】风控中的特征评价指标(一)——IV和WOE
    【转】Python调用C语言动态链接库
    基于蒙特卡洛树搜索(MCTS)的多维可加性指标的异常根因定位
    正则表达式全集
    基于ray的分布式机器学习(二)
    基于ray的分布式机器学习(一)
  • 原文地址:https://www.cnblogs.com/lfri/p/16175425.html
Copyright © 2020-2023  润新知