• 渐进式 shiro - shiro + jwt+salt (一)


    本文简述:shiro基本使用

    shiro 最常用得就是用来 处理登录接口 以及 处理其他需要在请求头中携带token的接口 这么一个框架。

    Shiro 架构设计:

    • Authentication: 认证。如用户的登录。
    • Authorization: 授权。用户是否有权限访问指定 URL 等。
    • Cryptography: 密码学。如密码的加密。
    • Session Management: Session 管理。
    • Web Integration: Web 集成。Shiro 不依赖于容器。

    shiro 登录认证流程:

    1. 用户访问登录接口 /login, 输入登录账号和密码被封装成 UsernamePasswordToken 对象.
    2. 接下来,登录服务中调用 subject.login() 方法,Shiro 立即进入用户认证过程,此认证过程做的事情式: 检验用户账号和密码是否正确

    例子:使用 shiro.ini 固定配置文件

    上面我们简述了 shiro 的结构以及最最简单得认证流程,你可能不太理解,不过没有关系,举个例子说明。

    想一下我们最最简单的登录流程是怎么样的?用户输入用 户名和密码,然后访问登录接口,我们在 "/login"接口中接收到数据后,与数据库中的用户进行查找比较,返回该用户是否输入了正确的账号和密码。

    我们先创建一个简单的示例来说明:

    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-coreartifactId>
        <version>1.10.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建一个用户信息文件: src/main/resources/shiro.ini ,此文件可以理解为 数据库

    # ---------------------
    # users用来定义用户
    [users]
    # 用户名 = 密码,角色1,角色2...
    admin = 123456, admin
    guest = guest, guest
    aa = 123456, guest
    # ---------------------
    # roles用来定义角色
    [roles]
    # 角色 = 权限 (* 代表所有权限)
    admin = *
    # 角色 = 权限 (* 代表所有权限)
    guest = select
    aa = update
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    登录

    @SpringBootTest
    class ShiroTest {
    
        @Test
        public void testShiroAuth(){
            // 1.创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            // 2.设置 realm
            securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
            // 3.设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
            // 4.获取当前主体对象
            Subject subject = SecurityUtils.getSubject();
            // 5.将用户登录时的账号和密码封账成 UsernamePasswordToken 对象.
            // 此处写入 "admin", "password",表示用户输入的账号,密码
            UsernamePasswordToken token = new UsernamePasswordToken("admin", "password");
    
            try {
                System.out.println("登录认证前状态:" + subject.isAuthenticated());
                subject.login(token);
                System.out.println("登录认证后状态:" + subject.isAuthenticated());
    
                boolean permitted = subject.isPermitted("update");
                System.out.println(permitted);
            } catch (UnknownAccountException e) {
                System.out.println("用户不存在");
            } catch (IncorrectCredentialsException e) {
                System.out.println("账号或密码不正确");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    shiro内部提供了两种方法创建 Realm 对象,这个小栗子中我们通过shiro提供得类IniRealm来读取文件 shiro.ini中写死得数据:

    1. 通过配置文件创建 Realm 对象: IniRealm 内封装了通过配置文件读取用户信息得方法,将此实例交给 SecurityManager 管理.
    2. 通过 springboot 中注解方式创建 Realm 对象: 实现接口 AuthorizingRealm,并在其 doGetAuthenticationInfo 方法中对登录信息进行校验处理 ,最终注入为 Bean

    springboot 中使用 shiro

    上面的代码,我们在一个测试方法中模拟了 shiro 的整个认证流程。
    但是如果要在 springboot 中集成 shiro,可没有这么简单,我们需要将上面的 1,2,3,4,5 步骤分别拆开在不同的文件中,以达到更好的解耦性以及可扩展性,其实就是按照 java 的标准来。

    因此我们首先需要了解一点 shiro 配置相关的东西。

    shiro 的配置类主要包含三个 bean:

    • SecurityManager:安全管理器
    • shiroFilter:过滤器
    • Realm:realm 认证流程
    1. shiro 作为一个用来管理 http 请求框架,里面有非常多细节的功能,哪些功能启用,哪些功能不启用,是由控制中枢决定: SecurityManager
    2. shiro 集成到一个 springboot 项目中,而项目有许许多多的接口,哪些接口无需登录可以直接访问,哪些接口必须登录后带着 token 访问,这是由过滤器决定: shiroFilter
    3. shiro 作为一个通用的认证授权框架,并非只能在 springboot 中使用,因此框架专门将认证流程抽象后独立出来,产生了一个新的的概念:Realm.

    到这里,shiro 中的三个核心概念你应该有所了解了,剩下的内容就式关于如何使用shiro: 在 springboot 中如何集成 shiro?

    springboot 集成 shiro

    在 springboot 中集成 shiro,还得再赘述几句。

    spring 是用来管理 javaBean 对象的容器,集成 shiro 时,shiro 也同样要将上面提到的几个核心对象交给 spring 去管理。

    先添加依赖

    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-spring-boot-web-starterartifactId>
        <version>1.10.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    首先是配置,shiro 的三大核心,通过 @Bean 注册为spring中的bean对象交给 spring 去管理

    @Configuration
    public class ShiroConfig {
        /**
         * `SecurityManager`:安全管理器
         */
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userRealm());
            return securityManager;
        }
    
        /**
         * `shiroFilter`:过滤器
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // Shiro的核心安全接口,这个属性是必须的
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 定义过滤链
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            // 登录,不需要拦截的访问
            filterChainDefinitionMap.put("/login", "anon");
            // 其余所有接口都需要认证拦截
            filterChainDefinitionMap.put("/**","authc");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
        /**
         * `Realm`:realm 认证流程
         */
        @Bean
        public UserRealm userRealm() {
            return new UserRealm();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在上面得演示代码中有报错,这是因为 UserRealm还不知道在哪里。回顾一下第一个的简单例子,我们是从shiro.ini这个文件中读取的用户数据,由shiro将文件中得数据封装成了Realm.

    在实际项目中,我们需要从数据库中读取用户信息然后再比较,这个过程就需要我们自己来写。

    如何做呢?实现AuthorizingRealm即可。

    注意,userService 使用 mybatis 生成。

    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        /**
         * 授权
         *
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        /**
         * 验证:用户登录
         * 
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            UsernamePasswordToken accessToken = (UsernamePasswordToken) authenticationToken;
    
            // 查询用户
            UserEntity user = Optional.ofNullable(userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, accessToken.getUsername())))
                    .orElseThrow(() -> new UnknownAccountException("账号或密码不正确"));
    
            //  验证密码
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    user,
                    user.getPassword(),
                    getName()
            );
            return authenticationInfo;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    我们实现 UserRealm 就是实现认证过程,里面有两个方法,单词十分的相近,并且登录认证使用下面那个 doGetAuthenticationInfo 方法.

    1. 在这一步中,doGetAuthenticationInfo 方法.有一个入参类型AuthenticationToken,它就是登陆时用户传入的账号和密码。没错,一旦我们重写之后,调用subject.login(),就会立即执行这一方法中的代码,你可以在此处打个断点感受一下。

    2. 我们将其向下转型,转换为 UsernamePasswordToken 这个类。UsernamePasswordTokenAuthenticationToken的一个实现类,比它多了若干的方法。

    3. 转换的目的是UsernamePasswordToken提供了一个获取用户名的方法,我们调用此方法拿到用户传过来的 username ,根据此 username 去查询数据库

    4. 接下来我们使用 shiro提供的类 SimpleAuthenticationInfo构造一个对象并返回。这里你一定会感到疑惑,怎么就直接返回了一个对象呢?正常来说,从数据库查到了用户密码后,应该进行比较啊,equal()呢,x == y 呢?

    5. shiro 为了将比较结果存储起来,将比较这一步骤也给封装起来了: 因此只要在方法中返回了 SimpleAuthenticationInfo这个类,接下来shiro就会在内部自动执行比较操作。

    好的,接下来,你可以创建一个 controller,在数据库里创建一个用户,完整感受一下。

    @RestController
    public class UserController {
        @PostMapping("/login")
        public AjaxResult loginUser(@RequestBody UserEntity userVo) {
            Subject subject = SecurityUtils.getSubject();
    
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword());
            usernamePasswordToken.setRememberMe(true);
            try {
                subject.login(usernamePasswordToken);
                System.out.println("登录成功");
            } catch (AuthenticationException ae) {
                System.out.println("登录失败: " + ae.getMessage());
                return AjaxResult.error("登录失败: " + ae.getMessage());
            }
            return AjaxResult.success(blogDto);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    看看阿里文娱怎么建设开放平台,这就是专业~
    Vue项目中配置eslint
    markdown 编辑器实现双屏同步滚动
    开机启动流程
    puzzle(021.1)挑选消除问题
    【基础】Java面试题
    一款超强的 Python 分析工具!
    Vue中的深度监听(Deep Watch):详细解析与实际示例
    Python调用C++
    SpringCloud使用-集中配置组件SpringCloudConfig
  • 原文地址:https://blog.csdn.net/win7583362/article/details/127899210