• SpringBoot整合Shiro和加密


    Shiro

    • 是一个强大灵活的开源安全框架,提供认证、授权、会话管理以及密码加密等功能。
    • 关于shiro的介绍,网上一大堆,但是,shiro整合springboot步骤和套路缺很少。下面是自己学习时候的一个总结

    其基本功能点如下图所示

    在这里插入图片描述

    • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
    • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
    • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
    • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
    • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
    • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
    • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
    • Testing:提供测试支持;
    • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
    • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

    shiro三个顶级概念

    • Subject,SecurityManagerRealms

    在这里插入图片描述

    • Subject是安全领域里的“当前执行用户“。可以是一个人,或者是第三方服务,守护进程帐户,定时任务等等——可以是基本上任何正与软件交互的事物。Subject的实例都会(也是必须)绑定一个SecurityManager,对Subject的操作会转为Subject与SecurityManager之间的交互。

    • SecurityManager是Shiro架构的核心,像个“保护伞”一样协调内部组件运作。不过一旦SecurityManager配置完成,它就被放一边去了,通常开发人员只需要使用Subject

      • 简单来说,使用Shiro时需要把用SecurityManager管理器去集成Realms-
    • Realms在Shiro和你的安全数据之间扮演“桥梁”或“连接器”的角色。当需要用到安全数据比如用户帐号来进行认证或授权时,Shiro会从应用配置的一个或多个Realms中来查找。

      • Realms类需要自己来创建。实现AuthorizingRealm接口。
      • 重写两个方法,一个是授权,一个是认证

    在这里插入图片描述

    Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;

    SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

    Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

    Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

    Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

    SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

    SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

    CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

    Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。

    案例

    1.导入依赖

     
            
                org.projectlombok
                lombok
            
            
                org.springframework.boot
                spring-boot-starter
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
            
                org.mybatis.spring.boot
                mybatis-spring-boot-starter
                2.1.1
            
            
                mysql
                mysql-connector-java
            
            
                org.springframework.boot
                spring-boot-starter-jdbc
            
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                org.apache.tomcat.embed
                tomcat-embed-jasper
            
            
                jstl
                jstl
                1.2
            
            
                org.apache.shiro
                shiro-spring-boot-starter
                1.6.0
            
            
                org.apache.shiro
                shiro-ehcache
                1.5.2
            
            
                org.springframework.boot
                spring-boot-starter-data-redis
            
    
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    2.配置文件

    server:
      port: 8080
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis
        username: root
        password: rot
      mvc:
        view:
          suffix: .jsp
          prefix: /
    
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.item.pojo
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.写配置类

    配置类需要三个类,一般来说,可以直接配置自己要拦截那些请求

    • ShiroFilterFactoryBean过滤拦截,用于来拦截一些请求。一般会放行公共资源

      •   hiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
                  System.out.println("进行拦截");
                  bean.setSecurityManager(securityManager);
                  Map map = new HashMap<>();
                  map.put("/user/login","anon"); // 公共资源  anon->不用验证
                  map.put("/user/register","anon"); // 公共资源  anon->不用验证
                  map.put("/register.jsp","anon"); // 公共资源  anon->不用验证
                  map.put("/**","authc"); // 受限资源   authc->需要验证
                  //没有认证自动跳到登录页面
                  bean.setLoginUrl("/login.jsp");
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
    • DefaultWebSecurityManager默认的安全管理器

      • 只需要整合Realm
    • Realm安全实体数据源,需要自己去定义一个

      •   public class UserRealm extends AuthorizingRealm {
          //    授权
              @Override
              protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
                  return null;
              }
          //  认证
              @Override
              protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                 return null;
              }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

      @Configuration
      public class ShiroConfig {
      // 过滤
      @Bean
      public ShiroFilterFactoryBean shiroFilterFactoryBean(@PathVariable(“securityManager”) DefaultSecurityManager securityManager){
      ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
      bean.setFilterChainDefinitionMap(map);
      return bean;
      }
      //管理器
      @Bean

      public DefaultWebSecurityManager securityManager(@PathVariable Realm realm){
          DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
          bean.setRealm(realm);
          return bean;
      }
      //realm
      @Bean
      public Realm realm(){
          return userRealm;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      }

    4.写业务

    4.1 Dao层

    @Mapper
    public interface UserMapper {
        int save(User user);
        User getUser(Map map);
    
        // 根据名字查询角色信息
        User findRoleByName(String username);
        List findPresById(int id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.2 Service

    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserMapper userMapper;
        @Override
        public void register(User user) {
            userMapper.save(user);
        }
    
        @Override
        public User getUser(Map map) {
            return userMapper.getUser(map);
        }
    
        @Override
        public User findRoleByName(String username) {
            return userMapper.findRoleByName(username);
        }
    
        @Override
        public List findPresById(int id) {
            return userMapper.findPresById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4.3 Collerction

    • 在shiro中,如果要进行登录验证,那么可以在登录之前获取Token,然后直接用shiro的登录去登录

      @RequestMapping(“login”)
      public String login(String username,String password){
      // 认证主体
      Subject subject = SecurityUtils.getSubject();
      try{
      // 获取令牌 new UsernamePasswordToken(username,password)
      subject.login(new UsernamePasswordToken(username,password));
      return “redirect:/index.jsp”;
      }catch (UnknownAccountException e){
      System.out.println(“用户名错误”);
      }catch (IncorrectCredentialsException e){
      System.out.println(“密码错误”);
      }
      return “/redirect:/login.jsp”;
      }

    认证

    这个时候可以去配置自己写的Realm类。配置登录验证

    //  认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // 获取身份信息,也就是用户名
            String principal = (String) token.getPrincipal();
            // 这里用的是工具类来获取业务层对像 ApplicationContextUtils 自己写的
            UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
            Map map = new HashMap<>();
            map.put("username",principal);
            User user = userService.getUser(map);
            if (!ObjectUtils.isEmpty(user)){
      // 用SimpleAuthenticationInfo来封装数据,shiro会自动来进行登录验证  this.getName()直接这样写
    return new SimpleAuthenticationInfo(user.getUserName(),user.getPassWord(),this.getName());
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ApplicationContextUtils.java

    @Component
    public class ApplicationContextUtils implements ApplicationContextAware {
        private static ApplicationContext context;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context = applicationContext;
        }
        //根据bean的名字获取工厂中指定的bean
        public static Object getBean(String beanName){
            System.out.println(context);
            return context.getBean(beanName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    授权

    //    授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("===========授权=====");
    //        获取身份->去数据库查询对应的权限和角色
            String principal = (String) principalCollection.getPrimaryPrincipal();
            UserService  userService = (UserService)ApplicationContextUtils.getBean("userService");
            // 根据身份信息查询数据
            User roleByName = userService.findRoleByName(principal);
    //        角色认证
            List roles = roleByName.getRoles();
            if (!CollectionUtils.isEmpty(roles)){
                // 用SimpleAuthorizationInfo来授予权限
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                roles.forEach(role -> {
                    // 添加角色
                    info.addRole(role.getName());
                    // 用字符串来表示权限,例如:user:*:*  拥有什么样的操作权限
                    List presById = userService.findPresById(role.getId());
                    if (!CollectionUtils.isEmpty(presById)){
                       presById.forEach(pres -> {
                           // 添加权限
                           info.addStringPermission(pres.getName());
                       });
                    }
                });
                return info;
            }
            return null;
        }
    
    • 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

    页面

    加密操作

    • shiro里面有一个类可以进行加密Md5Hash

    1.开启加密方式

    更改配置文件的Realm

        @Bean
        public Realm realm(){
            UserRealm userRealm = new UserRealm();
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    //        设置加密方式
            credentialsMatcher.setHashAlgorithmName("MD5");
            return userRealm;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.在添加信息的时候使用MD5加密

     Md5Hash md5Hash = new Md5Hash(user.getPassWord());
     user.setPassWord(md5Hash.toHex());
     user.setSalt(salt);
    
    • 1
    • 2
    • 3

    缓存

    • 使用redis缓存

    1.开启缓存配置(Realm最后成型)

        @Bean
        public Realm realm(){
            UserRealm userRealm = new UserRealm();
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    //        设置加密方式
            credentialsMatcher.setHashAlgorithmName("MD5");
    //        设置散列次数
            credentialsMatcher.setHashIterations(1024);
            userRealm.setCredentialsMatcher(credentialsMatcher);
    //        设置开启缓存,用自己的缓存了类
            userRealm.setCacheManager(new RedisCacheManager());
            // 开启缓存
            userRealm.setCachingEnabled(true);
            userRealm.setAuthenticationCachingEnabled(true); //开启认证缓存
            userRealm.setAuthenticationCacheName("authenticationCache");// 设置认证缓存名字
            userRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
            userRealm.setAuthorizationCacheName("authorizationCache"); // 设置授权缓存名字
            return userRealm;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.写一个缓存映射类,只需要继承CacheManager

    public class RedisCacheManager implements CacheManager {
        @Override
        public  Cache getCache(String cacheName) throws CacheException {
       // cacheName 的值就是在realm里面设置的授权名或者验证名
            return new RedisCache<>(cacheName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.配置一个缓存类

    • 类中方法和reids的操作基本相同。

    • 这里我用的是缓存一个hash数据

      public class RedisCache implements Cache {
      private String cacheName;
      public RedisCache() {
      }
      public RedisCache(String cacheName) {
      this.cacheName = cacheName;
      }

      @Override
      public V get(K k) throws CacheException {
          return (V) getRedisTemple().opsForHash().get(this.cacheName,k.toString());
      }
      
      @Override
      public V put(K k, V v) throws CacheException {
          getRedisTemple().opsForHash().put(this.cacheName,k.toString(),v);
          return null;
      }
      
      @Override
      public V remove(K k) throws CacheException {
          return (V) getRedisTemple().opsForHash().delete(this.cacheName);
      }
      
      @Override
      public void clear() throws CacheException {
          getRedisTemple().delete(this.cacheName);
      }
      
      @Override
      public int size() {
          return getRedisTemple().opsForHash().size(this.cacheName).intValue();
      }
      
      @Override
      public Set keys() {
          return getRedisTemple().opsForHash().keys(this.cacheName);
      }
      
      @Override
      public Collection values() {
          return null;
      }
      
      • 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

      /// 获取redis的操作了类RedisTemplate
      private RedisTemplate getRedisTemple(){
      RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean(“redisTemplate”);
      // 开启序列化的方式
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      return redisTemplate;
      }
      }

    ##以上是自己对学习shiro的一个总结,如果有什么不足之处,还希望各位小伙伴谅解。大家一起深造

  • 相关阅读:
    小程序 步骤条组件
    Node.js安装使用
    写小论文框架,该如何下手?
    Meta-Transformer:基于Transformer的多模态感知,融合Token化与共享编码
    最小可用产品MVP,投石问路
    数据治理-数据仓库和商务智能-部分内容
    汇编:寄存器/register,基础概念
    【C++】
    hadoop 大数据环境配置 同步时间 centos服务器时间同步 linux 安装ntp服务更新时间 hadoop(六)
    PHP文字识别Tesseract (CentOS)
  • 原文地址:https://blog.csdn.net/web17886480312/article/details/126514985