Shiro实战Demo

Shiro 使用

hello shiro

  1. 首先创建Maven项目并引入最新的shiro依赖

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.7.0</version>
            </dependency>
    
  2. 在项目的resources目录下新建shiro.ini文件,并在文件中配置用户名和密码

    [users]
    zhangsan=123
    
  3. src目录下新建类HelloShiro

    public class HelloShiro {
    
        public static void main(String[] args) {
    
            // 1.创建默认的SecurityManager管理器
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
            // 2. 加载Realm
            defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
    
            // 3. 将SecurityManager交给SecurityUtils管理
            SecurityUtils.setSecurityManager(defaultSecurityManager);
    
            // 4. 获取认证授权主体
            Subject subject = SecurityUtils.getSubject();
    
            // 5. 创建具有用户名和密码的认证授权token
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
    
            try {
                // 6. 进行登录,如果登录失败则会进入异常
                subject.login(token);
                System.out.println("是否登录成功:" + subject.isAuthenticated());
            } catch (UnknownAccountException e) {
                System.out.println("帐号错误");
                e.printStackTrace();
            } catch (IncorrectCredentialsException e) {
                System.out.println("密码错误");
                e.printStackTrace();
            }
    
        }
    }
    

Hello Shiro的登录流程查看

shiro登录认证过程讲解

自定义Realm实现认证授权

自定义Realm实现认证

  1. Maven引入shiro依赖

  2. 自定义CustomRealm类并继承AuthorizingRealm抽象类

    public class CustomRealm extends AuthorizingRealm {
        
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            // 获取认证帐号
            String username = (String) token.getPrincipal();
            
            // 其中“zhangsan”和“123”是从数据库中查出来的(这里省略了数据库查询操作)
            if (username != null && username.equals("zhangsan")) {
                return new SimpleAuthenticationInfo("zhangsan", "123", this.getName())''
            }
    
            return null;
        }
    }
    
  3. 新建类进行登录,验证自定义的Realm。在该类中因为是自定义的Realm,所以在SecurityManager加载Realm与HelloShiro有所不同, 具体如下:

    public class ConstomRealmAuthentication {
        public static void main(String[] args) {
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
            // 这里直接加载自定义的Realm
            defaultSecurityManager.setRealm(new CustomRealm());
    
            SecurityUtils.setSecurityManager(defaultSecurityManager);
    
            Subject subject = SecurityUtils.getSubject();
    
            try {
                subject.login(new UsernamePasswordToken("zhangsan", "123"));
                System.out.println("登录是否成功:" + subject.isAuthenticated());
            } catch (UnknownAccountException e) {
                System.out.println("账户错误");
                e.printStackTrace();
            } catch (IncorrectCredentialsException e) {
                System.out.println("密码错误");
                e.printStackTrace();
            }
        }
    }
    

自定义Realm实现授权

步骤大体与自定义Realm认证相同,只不过授权需要更改CustomRealm类中的doGetAuthorizationInfo的方法。其自定义Realm类如下:

public class CustomRealm extends AuthorizingRealm {

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        // 获取登录名
        String username = (String) principals.getPrimaryPrincipal();

        // 这里的admin,user:update:01, order:*:*,字符串假设是从数剧库中查到的
        if (username.equals("zhangsan")) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRole("admin");
            info.addStringPermission("user:update:01");
            info.addStringPermission("order:*:*");
            return info;
        }
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String) token.getPrincipal();

        if (username != null && username.equals("zhangsan")) {
            return new SimpleAuthenticationInfo("zhangsan", "123", this.getName());
        }

        return null;
    }
}

其中对用户角色和权限的验证代码如下:

try {
	 subject.login(new UsernamePasswordToken("zhangsan", "123"));
     System.out.println("登录是否成功:" + subject.isAuthenticated());

     boolean admin = subject.hasRole("admin");
     System.out.println("用户具有admin角色:" + admin);

     boolean permitted = subject.isPermitted("user:update:01");
     System.out.println("用户具有对user模块的01实例进行update的权限:" + permitted);

     boolean permitted1 = subject.isPermitted("order:update:*");
     System.out.println("用户具有对order模块的所有实例进行update权限:" + permitted1);

 } catch (UnknownAccountException e) {
     System.out.println("账户错误");
     e.printStackTrace();
 } catch (IncorrectCredentialsException e) {
     System.out.println("密码错误");
     e.printStackTrace();
 }

在权限中使用user:update:01是资源通配符,其规则是 资源标识符:操作:对象实例 ID ,表示为可对那个资源的那个实例进行什么操作。

加密

  1. 首先通过shiro中的Md5Hash类来加密,模拟从数据库中取出的已经加密的密码

    // 原密码:123456, salt:7G&;-,散列次数:1024
    Md5Hash ceshi = new Md5Hash("123456", "7G&;-", 1024);
    System.out.println(ceshi.toHex()); // output:1e923b90f26d3078e0c6611427d1ace9
    
  2. 如果要实现加密认证,首先更改原本的自定义Realm中的认证方法

        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            String username = (String) token.getPrincipal();
            
            if (username != null && username.equals("zhangsan")) {
                
                // 这里的密码和salt是直接从数据库中查出来的
                return new SimpleAuthenticationInfo("zhangsan", 
                        "1e923b90f26d3078e0c6611427d1ace9", 
                        ByteSource.Util.bytes("ceshi"), this.getName());
            }
    
            return null;
        }
    
  3. 然后更改认证的类,在自定义realm中设置相应的密码匹配器

    DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
    CustomRealm realm = new CustomRealm();
    
    // 这里设置认证凭证匹配器
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // 设置加密方式
    credentialsMatcher.setHashAlgorithmName("md5");
    // 设置散列次数
    credentialsMatcher.setHashIterations(1024);
    realm.setCredentialsMatcher(credentialsMatcher);
    
    defaultSecurityManager.setRealm(realm);
    
    SecurityUtils.setSecurityManager(defaultSecurityManager);
    
    Subject subject = SecurityUtils.getSubject();
    
    try {
        UsernamePasswordToken zhangsan = new UsernamePasswordToken("zhangsan", "123456");
        subject.login(zhangsan);
        System.out.println("登录是否成功:" + subject.isAuthenticated());
    } catch (UnknownAccountException e) {
        System.out.println("账户错误");
        e.printStackTrace();
    } catch (IncorrectCredentialsException e) {
        System.out.println("密码错误");
        e.printStackTrace();
    }
    

spring boot整合shiro

初始化数据库

首先创建数据库,数剧看中有5张表,分别是用户表,角色表, 用户角色关系表, 权限表,角色权限表

# 用户表
CREATE TABLE `t_users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(40) DEFAULT NULL,
  `password` varchar(45) DEFAULT NULL,
  `salt` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 角色表
CREATE TABLE `t_roles` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 用户角色关系表
CREATE TABLE `t_user_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `role_id` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 权限表
CREATE TABLE `t_perms` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `url` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

# 权限角色关系表
CREATE TABLE `t_role_perm` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role_id` int NOT NULL,
  `perm_id` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

接下来引入依赖

在pom文件中引入mysql数据库连接坐标,mybatis,shiro、spring boot的坐标

整合spring boot实现登录认证

spring 项目中使用shiro做认证授权时,会启动shiro的WebEnvironment,然后通过WebEnvironment中的ShiroFilter对请求进行认证授权。

  1. 因为使用了Spring Boot,因此不能像之前那样直接在main方法中编写登录的认证,需要将shiro的SecurityManager注入到spring容器中,交给spring进行管理。shiro的配置如下:

    @Configuration
    public class ShiroConfig {
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
    
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
            
            // 配置请求的过滤策略
            Map<String, String> map = new HashMap<>();
            map.put("/register.jsp", "anon");
            map.put("/login.jsp", "anon");
            map.put("/user/login", "anon");
            map.put("/user/register", "anon");
            map.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm) {
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            defaultWebSecurityManager.setRealm(realm);
    
            return defaultWebSecurityManager;
        }
    
        @Bean
        public Realm realm() {
            CustomerRealm customerRealm = new CustomerRealm();
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName("md5");
            credentialsMatcher.setHashIterations(1024);
            customerRealm.setCredentialsMatcher(credentialsMatcher);
    
            return customerRealm;
        }
    }
    
    

    在上传代码中有提到请求的过滤策略,其中anon、authcDefaultFilter枚举中的值(具体含义可以查看源码或shiro文档,在此不在赘述😂)。其可选值如下:

在这里插入图片描述

  1. 定义自定义realm

    在自定义realm中需要根据用户账户查询数据库,因此需要用到一个工具类,该工具类能够获取Dao的bean。

    • 根据名字获取bean的工具类

      @Component
      public class ApplicationContextUtil implements ApplicationContextAware {
      
          private static ApplicationContext applicationContext;
      
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              ApplicationContextUtil.applicationContext = applicationContext;
          }
      
          /**
           * 根据名字获取bean
           * @param name
           * @return
           */
          public static Object getBean(String name) {
      
              return applicationContext.getBean(name);
          }
      }
      
    • 自定义Realm

      public class CustomerRealm extends AuthorizingRealm {
          /**
           * 授权
           * @param principals
           * @return
           */
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
      
              String username = (String) principals.getPrimaryPrincipal();
      
              UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl");
              RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleServiceImpl");
              User user = userService.selectUserAndRolesByUsername(username);
              if (user != null) {
                  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
      
                  for (Roles role: user.getRoles()) {
                      info.addRole(role.getName());
      
                      // 获取权限
                      List<Roles> roles = roleService.selectRoleAndPermByRoleId(role.getId());
                      for (Roles r : roles) {
                          r.getPerms().forEach(item -> info.addStringPermission(item.getName()));
                      }
                  }
      
                  return info;
              }
      
              return null;
          }
      
          /**
           * 认证
           * @param token
           * @return
           * @throws AuthenticationException
           */
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
      
              String username = (String) token.getPrincipal();
      
              UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl");
              System.out.println("认证");
              User user = userService.selectByUsername(username);
              if (user != null) {
                  return new SimpleAuthenticationInfo(username,
                          user.getPassword(),
                          // ByteSource.Util.bytes(user.getSalt()),
                          new MyByteSource(user.getSalt()),
                          this.getName());
              }
      
              return null;
          }
      }
      
  2. 编写Controller层程序

    @RequestMapping("/login")
    public String login(String username, String password) {
    
        Subject subject = SecurityUtils.getSubject();
    
        try {
            subject.login(new UsernamePasswordToken(username, password));
            return "redirect:/index.jsp";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用戶名錯誤");
            return "redirect:/login.jsp";
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密碼错误");
            return "redirect:/login.jsp";
        }
    }
    

    其中关于授权的验证有多种方式,可以直接通过subject.hasRole("admin")这种方式,也可以通过@RequiresRoles(value = {"admin"})这种方式进行判断,当然授权也是如此。具体使用可以查看官方文档。

spring boot整合shiro使用redis缓存

  1. 添加redis的依赖

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.4.0</version>
    </dependency>
    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>6.0.1.RELEASE</version>
    </dependency>
    
  2. 实现Redis缓存类

    在shiro缓存是通过CacheManager实现的,因此实现的连个Redis缓存类,一个需要实现CacheManager, 另一个需要实现Cache(因为CacheManager中的getCache方法需要返回的是实现Cache接口的类)

    • 实现CacheManager的类

      public class RedisCacheManager implements CacheManager {
      
          @Override
          public <K, V> Cache<K, V> getCache(String s) throws CacheException {
              System.out.println("redisCacheManager:" + s);
              return new RedisCache<K, V>(s);
          }
      }
      
    • 实现Cache的类

      public class RedisCache<K, V> implements Cache<K, V> {
      
          private String cacheName;
      
          public RedisCache() {
          }
      
          public RedisCache(String cacheName) {
              this.cacheName = cacheName;
          }
      
          @Override
          public V get(K k) throws CacheException {
              System.out.println("redisCache-get:" + k);
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      //        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
              return (V) redisTemplate.opsForHash().get(this.cacheName, k.toString());
          }
      
          @Override
          public V put(K k, V v) throws CacheException {
              System.out.println("redisCanche-put:" + k);
              System.out.println("-------------  :" + v);
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      //        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
              redisTemplate.opsForHash().put(this.cacheName, k.toString(), v);
              return null;
          }
      
          @Override
          public V remove(K k) throws CacheException {
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
              return (V) redisTemplate.opsForHash().delete(this.cacheName, k.toString());
          }
      
          @Override
          public void clear() throws CacheException {
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
              redisTemplate.delete(this.cacheName);
          }
      
          @Override
          public int size() {
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
              return redisTemplate.opsForHash().size(this.cacheName).intValue();
          }
      
          @Override
          public Set<K> keys() {
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
              return redisTemplate.opsForHash().keys(this.cacheName);
          }
      
          @Override
          public Collection<V> values() {
              RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
              redisTemplate.setKeySerializer(new StringRedisSerializer());
              redisTemplate.setHashKeySerializer(new StringRedisSerializer());
              return redisTemplate.opsForHash().values(this.cacheName);
          }
      }
      
      

    整个spring boot集成shiro并使用redis缓存的代码

    https://github.com/StudyRecording/shiro-learn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值