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.导入依赖

 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
</dependencies>

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

3.写配置类

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

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

    • hiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
              System.out.println("进行拦截");
              bean.setSecurityManager(securityManager);
              Map<String, String> 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");
      
  • 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;
          }
           
      
@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;
    }
}

4.写业务

4.1 Dao层

@Mapper
public interface UserMapper {
    int save(User user);
    User getUser(Map<String,String> map);

    // 根据名字查询角色信息
    User findRoleByName(String username);
    List<Pres> findPresById(int id);
}

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<String, String> map) {
        return userMapper.getUser(map);
    }

    @Override
    public User findRoleByName(String username) {
        return userMapper.findRoleByName(username);
    }

    @Override
    public List<Pres> findPresById(int id) {
        return userMapper.findPresById(id);
    }
}

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<String,String> 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;
    }
}
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);
    }
}
授权
//    授权
    @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<Role> roles = roleByName.getRoles();
        if (!CollectionUtils.isEmpty(roles)){
            // 用SimpleAuthorizationInfo来授予权限
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            roles.forEach(role -> {
                // 添加角色
                info.addRole(role.getName());
                // 用字符串来表示权限,例如:user:*:*  拥有什么样的操作权限
                List<Pres> presById = userService.findPresById(role.getId());
                if (!CollectionUtils.isEmpty(presById)){
                   presById.forEach(pres -> {
                       // 添加权限
                       info.addStringPermission(pres.getName());
                   });
                }
            });
            return info;
        }
        return null;
    }

页面

  • thymeleaf和shiro
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>操作</h1>
    <a href="${pageContext.request.contextPath}/user/loginout" >退出</a>
<ul>
    <shiro:hasAnyRoles name="admin,user">
        <li><a href="">用户管理</a> </li>
        <ul>
            <shiro:hasPermission name="user:save:*">
                <li><a href="">添加</a> </li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:delete:*">
                <li><a href="">删除</a> </li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:update:*">
            <li><a href="">修改</a> </li>
            </shiro:hasPermission>
            <shiro:hasPermission name="user:find:*">
            <li><a href="">查询</a> </li>
            </shiro:hasPermission>
        </ul>
    </shiro:hasAnyRoles>

    <shiro:hasRole name="user">
        <li><a href="">商品管理</a> </li>
        <li><a href="">订单管理</a> </li>
        <li><a href="">物流管理</a> </li>
    </shiro:hasRole>

</ul>
</body>
</html>

加密操作

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

1.开启加密方式

更改配置文件的Realm

    @Bean
    public Realm realm(){
        UserRealm userRealm = new UserRealm();
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//        设置加密方式
        credentialsMatcher.setHashAlgorithmName("MD5");
        return userRealm;
    }

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

 Md5Hash md5Hash = new Md5Hash(user.getPassWord());
 user.setPassWord(md5Hash.toHex());
 user.setSalt(salt);

缓存

  • 使用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;
    }

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

public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
   // cacheName 的值就是在realm里面设置的授权名或者验证名
        return new RedisCache<>(cacheName);
    }
}

3.配置一个缓存类

  • 类中方法和reids的操作基本相同。
  • 这里我用的是缓存一个hash数据
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 {
        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<K> keys() {
        return getRedisTemple().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<V> values() {
        return null;
    }
/// 获取redis的操作了类RedisTemplate
    private RedisTemplate getRedisTemple(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        // 开启序列化的方式
       redisTemplate.setKeySerializer(new StringRedisSerializer());
       redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

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

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值