SpringBoot整合Shiro和Redis的示例代码_java

首页 / 新闻资讯 / 正文

demo源码

此demo用SpringBoot+Shiro简单实现了登陆、注册、认证、授权的功能,并用redis做分布式缓存提高性能。

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 http://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.3.2.RELEASE</version>      </parent>      <groupId>com.ego</groupId>      <artifactId>shirodemo</artifactId>      <version>1.0-SNAPSHOT</version>      <properties>          <java.version>1.8</java.version>      </properties>      <dependencies>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>org.projectlombok</groupId>              <artifactId>lombok</artifactId>          </dependency>          <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-validation</artifactId>          </dependency>          <dependency>              <groupId>org.apache.httpcomponents</groupId>              <artifactId>httpcore</artifactId>              <version>4.4.13</version>          </dependency>            <!-- 引入jsp依赖 -->          <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>            <!-- shiro -->          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-spring-boot-starter</artifactId>              <version>1.5.3</version>          </dependency>            <!-- mybatis plus -->          <dependency>              <groupId>com.baomidou</groupId>              <artifactId>mybatis-plus-boot-starter</artifactId>              <version>3.4.1</version>          </dependency>            <!-- Druid数据源 -->          <dependency>              <groupId>com.alibaba</groupId>              <artifactId>druid-spring-boot-starter</artifactId>              <version>1.1.10</version>          </dependency>            <!-- Mysql -->          <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>              <scope>runtime</scope>          </dependency>          <dependency>              <groupId>io.springfox</groupId>              <artifactId>springfox-swagger2</artifactId>              <version>2.9.2</version>          </dependency>          <dependency>              <groupId>io.springfox</groupId>              <artifactId>springfox-swagger-ui</artifactId>              <version>2.9.2</version>          </dependency>          <dependency>              <groupId>log4j</groupId>              <artifactId>log4j</artifactId>              <version>1.2.17</version>          </dependency>          <dependency>              <groupId>org.apache.shiro</groupId>              <artifactId>shiro-ehcache</artifactId>              <version>1.5.3</version>          </dependency>          <!-- 引入redis -->          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-redis</artifactId>          </dependency>        </dependencies>    </project>  

配置yml文件

spring:    # 设置视图模板为jsp    mvc:      view:        prefix: /        suffix: .jsp    datasource:      type: com.alibaba.druid.pool.DruidDataSource      druid:        driver-class-name: com.mysql.cj.jdbc.Driver        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC        username: root        password: root        # 监控统计拦截的filters        filters: stat,wall,log4j,config        # 配置初始化大小/最小/最大        initial-size: 5        min-idle: 5        max-active: 20        # 获取连接等待超时时间        max-wait: 60000        # 间隔多久进行一次检测,检测需要关闭的空闲连接        time-between-eviction-runs-millis: 60000        # 一个连接在池中最小生存的时间        min-evictable-idle-time-millis: 300000        validation-query: SELECT 'x'        test-while-idle: true        test-on-borrow: false        test-on-return: false        # 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false        pool-prepared-statements: false        max-pool-prepared-statement-per-connection-size: 20    redis:      host: 127.0.0.1      port: 6379      password: abc123456      database: 0    mybatis-plus:    type-aliases-package: com.ego.pojo    configuration:      map-underscore-to-camel-case: true  

2.编写index,login,register三个JSP

<%--解决页面乱码--%>  <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>  <!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>index</title>  </head>  <body>      <form action="${pageContext.request.contextPath}/user/login" method="post">          用户名:<input type="text" name="username" > <br/>          密码  : <input type="text" name="password"> <br>          <input type="submit" value="登录">      </form>  </body>  </html>  <%--解决页面乱码--%>  <%@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>index</title>  </head>  <body>  <h1>系统主页</h1>  <a href="${pageContext.request.contextPath}/user/logout">退出用户</a>  <ul>    <%-- admin角色的用户能同时拥有用户管理和订单管理的权限,user角色的用户只拥有订单管理的权限 --%>    <shiro:hasRole name="admin">      <li>        <a href="">用户管理</a>      </li>    </shiro:hasRole>    <%-- admin角色的用户对订单有增删改查的权限,user角色的用户只能查看订单 --%>    <shiro:hasAnyRoles name="admin,user">      <li>        <a href="">订单管理</a>        <ul>          <shiro:hasPermission name="order:add:*">            <li><a href="">新增</a></li>          </shiro:hasPermission>          <shiro:hasPermission name="order:del:*">            <li><a href="">删除</a></li>          </shiro:hasPermission>          <shiro:hasPermission name="order:upd:*">            <li><a href="">修改</a></li>          </shiro:hasPermission>          <shiro:hasPermission name="order:find:*">            <li><a href="">查询</a></li>          </shiro:hasPermission>        </ul>      </li>    </shiro:hasAnyRoles>  </ul>  </body>  </html>  <%--解决页面乱码--%>  <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>  <!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>register</title>  </head>  <body>  <h1>用户注册</h1>  <form action="${pageContext.request.contextPath}/user/register" method="post">      用户名:<input type="text" name="username" > <br/>      密码  : <input type="text" name="password"> <br>      <input type="submit" value="立即注册">  </form>  </body>  </html>  

3.实现User、Role、Permission三个POJO

package com.ego.pojo;  import com.baomidou.mybatisplus.annotation.*;  import io.swagger.annotations.ApiModel;  import io.swagger.annotations.ApiModelProperty;  import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;  import java.io.Serializable;  import java.util.ArrayList;  import java.util.List;  /**   * @author 袁梦达 2019012364   */  @Data  @NoArgsConstructor  @AllArgsConstructor  @TableName("t_user")  @ApiModel("用户实体类")  public class User implements Serializable {      /** 数据库中设置该字段自增时该注解不能少 **/      @TableId(type = IdType.AUTO)      @ApiModelProperty(name = "id", value = "ID 主键")      private Integer id;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "username", value = "用户名")      private String username;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "password", value = "密码")      private String password;      @TableField(fill = FieldFill.INSERT)      @ApiModelProperty(name = "salt", value = "盐")      private String salt;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "age", value = "年龄")      private Integer age;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "email", value = "邮箱")      private String email;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "address", value = "地址")      private String address;      @TableField(exist = false)      private List<Role> roles = new ArrayList<>();  }  package com.ego.pojo;  import com.baomidou.mybatisplus.annotation.*;  import io.swagger.annotations.ApiModel;  import io.swagger.annotations.ApiModelProperty;  import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;  import java.io.Serializable;  import java.util.ArrayList;  import java.util.List;  /**   * @author 袁梦达 2019012364   */  @Data  @NoArgsConstructor  @AllArgsConstructor  @TableName("t_role")  @ApiModel("角色实体类")  public class Role implements Serializable {      /** 数据库中设置该字段自增时该注解不能少 **/      @TableId(type = IdType.AUTO)      @ApiModelProperty(name = "id", value = "ID 主键")      private Integer id;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "name", value = "角色名称")      private String name;      @TableField(exist = false)      private List<Permission> permissions = new ArrayList<>();  }  package com.ego.pojo;  import com.baomidou.mybatisplus.annotation.*;  import io.swagger.annotations.ApiModel;  import io.swagger.annotations.ApiModelProperty;  import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;  import java.io.Serializable;  /**   * @author 袁梦达 2019012364   */  @Data  @NoArgsConstructor  @AllArgsConstructor  @TableName("t_permission")  @ApiModel("权限实体类")  public class Permission implements Serializable {      /** 数据库中设置该字段自增时该注解不能少 **/      @TableId(type = IdType.AUTO)      @ApiModelProperty(name = "id", value = "ID主键")      private Integer id;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "name", value = "权限名称")      private String name;      @TableField(fill = FieldFill.INSERT_UPDATE)      @ApiModelProperty(name = "url", value = "权限菜单URL")      private String url;  }  

4.实现Controller、Service、Dao

这里dao采用了mybatis-plus

package com.ego.controller;  import com.ego.pojo.User;  import com.ego.service.UserService;  import org.apache.shiro.SecurityUtils;  import org.apache.shiro.authc.IncorrectCredentialsException;  import org.apache.shiro.authc.UnknownAccountException;  import org.apache.shiro.authc.UsernamePasswordToken;  import org.apache.shiro.subject.Subject;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.stereotype.Controller;  import org.springframework.web.bind.annotation.RequestMapping;  /**   * @author 袁梦达 2019012364   */  @Controller  @RequestMapping("/user")  public class UserController {      @Autowired      private UserService userService;      /**       * 用户登录       * @param username       * @param password       * @return       */      @RequestMapping("/login")      public String login(String username,String password){          // 获取当前登录用户          Subject subject = SecurityUtils.getSubject();          try {              // 执行登录操作              subject.login(new UsernamePasswordToken(username,password));              // 认证通过后直接跳转到index.jsp              return "redirect:/index.jsp";          } catch (UnknownAccountException e) {              e.printStackTrace();              System.out.println("用户名错误!");          } catch (IncorrectCredentialsException e) {              System.out.println("密码错误!");          } catch (Exception e) {          }          // 如果认证失败仍然回到登录页面          return "redirect:/login.jsp";      }      @RequestMapping("/logout")      public String logout(){          subject.logout();          // 退出后仍然会到登录页面       * 用户注册       * @param user      @RequestMapping("/register")      public String register(User user){              userService.register(user);              return "redirect:/login.jsp";          return "redirect:/register.jsp";  }  package com.ego.service.impl;  import com.ego.dao.mapper.UserMapper;  import com.ego.shiro.ShiroConstant;  import com.ego.utils.SaltUtil;  import org.apache.shiro.crypto.hash.Md5Hash;  import org.springframework.stereotype.Service;  @Service("userService")  public class UserServiceImpl implements UserService {      private UserMapper userMapper;      @Override      public void register(User user) {          //生成随机盐          String salt = SaltUtil.getSalt(ShiroConstant.SALT_LENGTH);          //保存随机盐          user.setSalt(salt);          //生成密码          Md5Hash password = new Md5Hash(user.getPassword(), salt, ShiroConstant.HASH_ITERATORS);          //保存密码          user.setPassword(password.toHex());          userMapper.insert(user);      public User findUserByUserName(String userName) {          return userMapper.findUserByUserName(userName);  import com.ego.dao.mapper.RoleMapper;  import com.ego.pojo.Role;  import com.ego.service.RoleService;  import java.util.List;  @Service("roleService")  public class RoleServiceImpl implements RoleService {      private RoleMapper roleMapper;      public List<Role> getRolesByUserId(Integer userId) {          return roleMapper.getRolesByUserId(userId);  import com.ego.dao.mapper.PermissionMapper;  import com.ego.pojo.Permission;  import com.ego.service.PermissionService;  @Service("permissionService")  public class PermissionServiceImpl implements PermissionService {      private PermissionMapper permissionMapper;      public List<Permission> getPermissionsByRoleId(Integer roleId) {          return permissionMapper.getPermissionsByRoleId(roleId);  package com.ego.dao.mapper;  import com.baomidou.mybatisplus.core.mapper.BaseMapper;  import org.apache.ibatis.annotations.Mapper;  import org.apache.ibatis.annotations.Select;  @Mapper  public interface UserMapper extends BaseMapper<User> {      @Select("SELECT u.id,u.username,u.password,u.salt,u.age,u.email,u.address FROM t_user u WHERE u.username = #{username}")      User findUserByUserName(String username);  public interface RoleMapper extends BaseMapper<Role> {      @Select("select r.id,r.name from t_role r left join t_user_role ur on ur.role_id = r.id where ur.user_id = #{userId}")      List<Role> getRolesByUserId(Integer userId);  public interface PermissionMapper extends BaseMapper<Permission> {      @Select("select p.id,p.name,p.url from t_permission p left join t_role_permission rp on rp.permission_id = p.id where rp.role_id = #{roleId}")      List<Permission> getPermissionsByRoleId(Integer roleId);  

5.实现SaltUtil和ApplicationContextUtil两个工具类

package com.ego.utils;  import java.util.Random;  /**   * @author 袁梦达 2019012364   */  public class SaltUtil {      public static String getSalt(int n){          char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();          StringBuilder sb = new StringBuilder();          for (int i = 0; i < n; i++) {              char aChar = chars[new Random().nextInt(chars.length)];              sb.append(aChar);          }          return sb.toString();      }  }  import org.springframework.beans.BeansException;  import org.springframework.context.ApplicationContext;  import org.springframework.context.ApplicationContextAware;  import org.springframework.stereotype.Component;  @Component  public class ApplicationContextUtil implements ApplicationContextAware {      public static ApplicationContext context;      @Override      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {          this.context = applicationContext;      /**       * 根据工厂中的类名获取类实例       */      public static Object getBean(String beanName){          return context.getBean(beanName);  

6.实现核心Shiro

package com.ego.utils;  import java.util.Random;  /**   * @author 袁梦达 2019012364   */  public class SaltUtil {      public static String getSalt(int n){          char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();          StringBuilder sb = new StringBuilder();          for (int i = 0; i < n; i++) {              char aChar = chars[new Random().nextInt(chars.length)];              sb.append(aChar);          }          return sb.toString();      }  }  package com.ego.utils;  import org.springframework.beans.BeansException;  import org.springframework.context.ApplicationContext;  import org.springframework.context.ApplicationContextAware;  import org.springframework.stereotype.Component;  /**   * @author 袁梦达 2019012364   */  @Component  public class ApplicationContextUtil implements ApplicationContextAware {      public static ApplicationContext context;      @Override      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {          this.context = applicationContext;      }      /**       * 根据工厂中的类名获取类实例       */      public static Object getBean(String beanName){          return context.getBean(beanName);      }  }  

7.实现Redis分布式缓存

package com.ego.shiro;  import com.ego.shiro.cache.RedisCacheManager;  import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  import org.apache.shiro.realm.Realm;  import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import java.util.HashMap;  import java.util.Map;  /**   * @author 袁梦达 2019012364   */  @Configuration  public class ShiroConfiguration {      //1.创建shiroFilter  //负责拦截所有请求      @Bean      public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){          ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();          //给filter设置安全管理器          shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);          //配置系统受限资源          //配置系统公共资源          Map<String,String> map = new HashMap<String,String>();          map.put("/user/login", "anon");          map.put("/user/register","anon");          map.put("/register.jsp","anon");          map.put("/index.jsp","authc");//authc 请求这个资源需要认证和授权          //默认认证界面路径          shiroFilterFactoryBean.setLoginUrl("/login.jsp");          shiroFilterFactoryBean.setFilterChainDefinitionMap(map);          return shiroFilterFactoryBean;      }      //2.创建安全管理器      public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){          DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();          //给安全管理器设置          defaultWebSecurityManager.setRealm(realm);          return defaultWebSecurityManager;      //3.创建自定义realm      public Realm getRealm(){          CustomerRealm customerRealm = new CustomerRealm();          // 设置密码匹配器          HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();          // 设置加密方式          credentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ALGORITHM_NAME.MD5);          // 设置散列次数          credentialsMatcher.setHashIterations(ShiroConstant.HASH_ITERATORS);          customerRealm.setCredentialsMatcher(credentialsMatcher);          // 设置缓存管理器          customerRealm.setCacheManager(new RedisCacheManager());          // 开启全局缓存          customerRealm.setCachingEnabled(true);          // 开启认证缓存并指定缓存名称          customerRealm.setAuthenticationCachingEnabled(true);          customerRealm.setAuthenticationCacheName("authenticationCache");          // 开启授权缓存并指定缓存名称          customerRealm.setAuthorizationCachingEnabled(true);          customerRealm.setAuthorizationCacheName("authorizationCache");          return customerRealm;  }  public class ShiroConstant {      /** 随机盐的位数 **/      public static final int SALT_LENGTH = 8;      /** hash的散列次数 **/      public static final int HASH_ITERATORS = 1024;      public interface HASH_ALGORITHM_NAME {          String MD5 = "MD5";  import com.ego.pojo.Permission;  import com.ego.pojo.Role;  import com.ego.pojo.User;  import com.ego.service.PermissionService;  import com.ego.service.RoleService;  import com.ego.service.UserService;  import com.ego.utils.ApplicationContextUtil;  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.util.CollectionUtils;  import org.springframework.util.ObjectUtils;  import java.util.List;  public class CustomerRealm extends AuthorizingRealm {      //授权      @Override      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {          // 获取主身份信息          String principal = (String) principals.getPrimaryPrincipal();          // 根据主身份信息获取角色信息          UserService userService = (UserService) ApplicationContextUtil.getBean("userService");          User user = userService.findUserByUserName(principal);          RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleService");          List<Role> roles = roleService.getRolesByUserId(user.getId());          if(!CollectionUtils.isEmpty(roles)){              SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();              roles.forEach(role -> {                  simpleAuthorizationInfo.addRole(role.getName());                  PermissionService permissionService = (PermissionService) ApplicationContextUtil.getBean("permissionService");                  List<Permission> permissions = permissionService.getPermissionsByRoleId(role.getId());                  if(!CollectionUtils.isEmpty(permissions)){                      permissions.forEach(permission -> {                          simpleAuthorizationInfo.addStringPermission(permission.getName());                      });                  }              });              return simpleAuthorizationInfo;          }          return null;      //认证      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {          String principal = (String) token.getPrincipal();          if(!ObjectUtils.isEmpty(user)){              return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), new CustomerByteSource(user.getSalt()),this.getName());  import org.apache.shiro.codec.Base64;  import org.apache.shiro.codec.CodecSupport;  import org.apache.shiro.codec.Hex;  import org.apache.shiro.util.ByteSource;  import java.io.File;  import java.io.InputStream;  import java.io.Serializable;  import java.util.Arrays;  //自定义salt实现  实现序列化接口  public class CustomerByteSource implements ByteSource, Serializable {      private byte[] bytes;      private String cachedHex;      private String cachedBase64;      public CustomerByteSource() {      public CustomerByteSource(byte[] bytes) {          this.bytes = bytes;      public CustomerByteSource(char[] chars) {          this.bytes = CodecSupport.toBytes(chars);      public CustomerByteSource(String string) {          this.bytes = CodecSupport.toBytes(string);      public CustomerByteSource(ByteSource source) {          this.bytes = source.getBytes();      public CustomerByteSource(File file) {          this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);      public CustomerByteSource(InputStream stream) {          this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(stream);      public static boolean isCompatible(Object o) {          return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;      public byte[] getBytes() {          return this.bytes;      public boolean isEmpty() {          return this.bytes == null || this.bytes.length == 0;      public String toHex() {          if (this.cachedHex == null) {              this.cachedHex = Hex.encodeToString(this.getBytes());          return this.cachedHex;      public String toBase64() {          if (this.cachedBase64 == null) {              this.cachedBase64 = Base64.encodeToString(this.getBytes());          return this.cachedBase64;      public String toString() {          return this.toBase64();      public int hashCode() {          return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;      public boolean equals(Object o) {          if (o == this) {              return true;          } else if (o instanceof ByteSource) {              ByteSource bs = (ByteSource) o;              return Arrays.equals(this.getBytes(), bs.getBytes());          } else {              return false;      private static final class BytesHelper extends CodecSupport {          private BytesHelper() {          public byte[] getBytes(File file) {              return this.toBytes(file);          public byte[] getBytes(InputStream stream) {              return this.toBytes(stream);